wx/wxprec.h already includes wx/defs.h (with other minor cleaning).
[wxWidgets.git] / src / unix / mimetype.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/mimetype.cpp
3 // Purpose: classes and functions to manage MIME types
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 23.09.98
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
11
12 // known bugs; there may be others!! chris elliott, biol75@york.ac.uk 27 Mar 01
13
14 // 1) .mailcap and .mimetypes can be either in a netscape or metamail format
15 // and entries may get confused during writing (I've tried to fix this; please let me know
16 // any files that fail)
17 // 2) KDE and Gnome do not yet fully support international read/write
18 // 3) Gnome key lines like open.latex."LaTeX this file"=latex %f will have odd results
19 // 4) writing to files comments out the existing data; I hope this avoids losing
20 // any data which we could not read, and data which we did not store like test=
21 // 5) results from reading files with multiple entries (especially matches with type/* )
22 // may (or may not) work for getXXX commands
23 // 6) Loading the png icons in Gnome doesn't work for me...
24 // 7) In Gnome, if keys.mime exists but keys.users does not, there is
25 // an error message in debug mode, but the file is still written OK
26 // 8) Deleting entries is only allowed from the user file; sytem wide entries
27 // will be preserved during unassociate
28 // 9) KDE does not yet handle multiple actions; Netscape mode never will
29
30 // TODO: this file is a mess, we need to split it and review everything (VZ)
31
32 // for compilers that support precompilation, includes "wx.h".
33 #include "wx/wxprec.h"
34
35 #ifdef __BORLANDC__
36 #pragma hdrstop
37 #endif
38
39 #if wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE
40
41 #ifndef WX_PRECOMP
42 #include "wx/string.h"
43 #endif
44
45 #include "wx/log.h"
46 #include "wx/file.h"
47 #include "wx/intl.h"
48 #include "wx/dynarray.h"
49 #include "wx/confbase.h"
50
51 #include "wx/ffile.h"
52 #include "wx/textfile.h"
53 #include "wx/dir.h"
54 #include "wx/utils.h"
55 #include "wx/tokenzr.h"
56 #include "wx/iconloc.h"
57 #include "wx/filename.h"
58
59 #include "wx/unix/mimetype.h"
60
61 // Not GUI dependent
62 #include "wx/gtk/gnome/gvfs.h"
63
64 // other standard headers
65 #include <ctype.h>
66
67 // this class extends wxTextFile
68 //
69 // VZ: ???
70 class wxMimeTextFile : public wxTextFile
71 {
72 public:
73 // constructors
74 wxMimeTextFile () : wxTextFile () {};
75 wxMimeTextFile(const wxString& strFile) : wxTextFile(strFile) {};
76
77 int pIndexOf(const wxString & sSearch, bool bIncludeComments = false, int iStart = 0)
78 {
79 size_t i = iStart;
80 int nResult = wxNOT_FOUND;
81 if (i >= GetLineCount())
82 return wxNOT_FOUND;
83
84 wxString sTest = sSearch;
85 sTest.MakeLower();
86 wxString sLine;
87
88 if (bIncludeComments)
89 {
90 while ( i < GetLineCount() )
91 {
92 sLine = GetLine(i);
93 sLine.MakeLower();
94 if (sLine.Contains(sTest))
95 nResult = (int) i;
96
97 i++;
98 }
99 }
100 else
101 {
102 while ( (i < GetLineCount()) )
103 {
104 sLine = GetLine(i);
105 sLine.MakeLower();
106 if ( ! sLine.StartsWith(wxT("#")))
107 {
108 if (sLine.Contains(sTest))
109 nResult = (int) i;
110 }
111
112 i++;
113 }
114 }
115
116 return nResult;
117 }
118
119 bool CommentLine(int nIndex)
120 {
121 if (nIndex < 0)
122 return false;
123 if (nIndex >= (int)GetLineCount() )
124 return false;
125
126 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
127 return true;
128 }
129
130 bool CommentLine(const wxString & sTest)
131 {
132 int nIndex = pIndexOf(sTest);
133 if (nIndex < 0)
134 return false;
135 if (nIndex >= (int)GetLineCount() )
136 return false;
137
138 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
139 return true;
140 }
141
142 wxString GetVerb(size_t i)
143 {
144 if (i > GetLineCount() )
145 return wxEmptyString;
146
147 wxString sTmp = GetLine(i).BeforeFirst(wxT('='));
148 return sTmp;
149 }
150
151 wxString GetCmd(size_t i)
152 {
153 if (i > GetLineCount() )
154 return wxEmptyString;
155
156 wxString sTmp = GetLine(i).AfterFirst(wxT('='));
157 return sTmp;
158 }
159 };
160
161 // in case we're compiling in non-GUI mode
162 class WXDLLEXPORT wxIcon;
163
164 // ----------------------------------------------------------------------------
165 // constants
166 // ----------------------------------------------------------------------------
167
168 // MIME code tracing mask
169 #define TRACE_MIME wxT("mime")
170
171 // give trace messages about the results of mailcap tests
172 #define TRACE_MIME_TEST wxT("mimetest")
173
174 // ----------------------------------------------------------------------------
175 // private functions
176 // ----------------------------------------------------------------------------
177
178 // there are some fields which we don't understand but for which we don't give
179 // warnings as we know that they're not important - this function is used to
180 // test for them
181 static bool IsKnownUnimportantField(const wxString& field);
182
183 // ----------------------------------------------------------------------------
184 // private classes
185 // ----------------------------------------------------------------------------
186
187
188 // This class uses both mailcap and mime.types to gather information about file
189 // types.
190 //
191 // The information about mailcap file was extracted from metamail(1) sources
192 // and documentation and subsequently revised when I found the RFC 1524
193 // describing it.
194 //
195 // Format of mailcap file: spaces are ignored, each line is either a comment
196 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
197 // A backslash can be used to quote semicolons and newlines (and, in fact,
198 // anything else including itself).
199 //
200 // The first field is always the MIME type in the form of type/subtype (see RFC
201 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
202 // "type" which means the same as "type/*", although I'm not sure whether this
203 // is standard.
204 //
205 // The second field is always the command to run. It is subject to
206 // parameter/filename expansion described below.
207 //
208 // All the following fields are optional and may not be present at all. If
209 // they're present they may appear in any order, although each of them should
210 // appear only once. The optional fields are the following:
211 // * notes=xxx is an uninterpreted string which is silently ignored
212 // * test=xxx is the command to be used to determine whether this mailcap line
213 // applies to our data or not. The RHS of this field goes through the
214 // parameter/filename expansion (as the 2nd field) and the resulting string
215 // is executed. The line applies only if the command succeeds, i.e. returns 0
216 // exit code.
217 // * print=xxx is the command to be used to print (and not view) the data of
218 // this type (parameter/filename expansion is done here too)
219 // * edit=xxx is the command to open/edit the data of this type
220 // * needsterminal means that a new interactive console must be created for
221 // the viewer
222 // * copiousoutput means that the viewer doesn't interact with the user but
223 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
224 // good example), thus it might be a good idea to use some kind of paging
225 // mechanism.
226 // * textualnewlines means not to perform CR/LF translation (not honored)
227 // * compose and composetyped fields are used to determine the program to be
228 // called to create a new message pert in the specified format (unused).
229 //
230 // Parameter/filename expansion:
231 // * %s is replaced with the (full) file name
232 // * %t is replaced with MIME type/subtype of the entry
233 // * for multipart type only %n is replaced with the nnumber of parts and %F is
234 // replaced by an array of (content-type, temporary file name) pairs for all
235 // message parts (TODO)
236 // * %{parameter} is replaced with the value of parameter taken from
237 // Content-type header line of the message.
238 //
239 //
240 // There are 2 possible formats for mime.types file, one entry per line (used
241 // for global mime.types and called Mosaic format) and "expanded" format where
242 // an entry takes multiple lines (used for users mime.types and called
243 // Netscape format).
244 //
245 // For both formats spaces are ignored and lines starting with a '#' are
246 // comments. Each record has one of two following forms:
247 // a) for "brief" format:
248 // <mime type> <space separated list of extensions>
249 // b) for "expanded" format:
250 // type=<mime type> BACKSLASH
251 // desc="<description>" BACKSLASH
252 // exts="<comma separated list of extensions>"
253 //
254 // (where BACKSLASH is a literal '\\' which we can't put here because cpp
255 // misinterprets it)
256 //
257 // We try to autodetect the format of mime.types: if a non-comment line starts
258 // with "type=" we assume the second format, otherwise the first one.
259
260 // there may be more than one entry for one and the same mime type, to
261 // choose the right one we have to run the command specified in the test
262 // field on our data.
263
264 // ----------------------------------------------------------------------------
265 // wxGNOME
266 // ----------------------------------------------------------------------------
267
268 // GNOME stores the info we're interested in in several locations:
269 // 1. xxx.keys files under /usr/share/mime-info
270 // 2. xxx.keys files under ~/.gnome/mime-info
271 //
272 // Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
273 // just before the field name.
274
275
276 void wxMimeTypesManagerImpl::LoadGnomeDataFromKeyFile(const wxString& filename,
277 const wxArrayString& dirs)
278 {
279 wxTextFile textfile(filename);
280 #if defined(__WXGTK20__) && wxUSE_UNICODE
281 if ( !textfile.Open(wxConvUTF8) )
282 #else
283 if ( !textfile.Open() )
284 #endif
285 return;
286
287 wxLogTrace(TRACE_MIME, wxT("--- Opened Gnome file %s ---"),
288 filename.c_str());
289
290 wxArrayString search_dirs( dirs );
291
292 // values for the entry being parsed
293 wxString curMimeType, curIconFile;
294 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
295
296 wxArrayString strExtensions;
297 wxString strDesc;
298
299 const wxChar *pc;
300 size_t nLineCount = textfile.GetLineCount();
301 size_t nLine = 0;
302 while ( nLine < nLineCount )
303 {
304 pc = textfile[nLine].c_str();
305 if ( *pc != wxT('#') )
306 {
307
308 wxLogTrace(TRACE_MIME, wxT("--- Reading from Gnome file %s '%s' ---"),
309 filename.c_str(), pc);
310
311 // trim trailing space and tab
312 while ((*pc == wxT(' ')) || (*pc == wxT('\t')))
313 pc++;
314
315 wxString sTmp(pc);
316 int equal_pos = sTmp.Find( wxT('=') );
317 if (equal_pos > 0)
318 {
319 wxString left_of_equal = sTmp.Left( equal_pos );
320 const wxChar *right_of_equal = pc;
321 right_of_equal += equal_pos+1;
322
323 if (left_of_equal == wxT("icon_filename"))
324 {
325 // GNOME 2:
326 curIconFile = right_of_equal;
327
328 wxFileName newFile( curIconFile );
329 if (newFile.IsRelative() || newFile.FileExists())
330 {
331 size_t nDirs = search_dirs.GetCount();
332
333 for (size_t nDir = 0; nDir < nDirs; nDir++)
334 {
335 newFile.SetPath( search_dirs[nDir] );
336 newFile.AppendDir( wxT("pixmaps") );
337 newFile.AppendDir( wxT("document-icons") );
338 newFile.SetExt( wxT("png") );
339 if (newFile.FileExists())
340 {
341 curIconFile = newFile.GetFullPath();
342 // reorder search_dirs for speedup (fewer
343 // calls to FileExist() required)
344 if (nDir != 0)
345 {
346 wxString tmp = search_dirs[nDir];
347 search_dirs.RemoveAt( nDir );
348 search_dirs.Insert( tmp, 0 );
349 }
350 break;
351 }
352 }
353 }
354 }
355 else if (left_of_equal == wxT("open"))
356 {
357 sTmp = right_of_equal;
358 sTmp.Replace( wxT("%f"), wxT("%s") );
359 sTmp.Prepend( wxT("open=") );
360 entry->Add(sTmp);
361 }
362 else if (left_of_equal == wxT("view"))
363 {
364 sTmp = right_of_equal;
365 sTmp.Replace( wxT("%f"), wxT("%s") );
366 sTmp.Prepend( wxT("view=") );
367 entry->Add(sTmp);
368 }
369 else if (left_of_equal == wxT("print"))
370 {
371 sTmp = right_of_equal;
372 sTmp.Replace( wxT("%f"), wxT("%s") );
373 sTmp.Prepend( wxT("print=") );
374 entry->Add(sTmp);
375 }
376 else if (left_of_equal == wxT("description"))
377 {
378 strDesc = right_of_equal;
379 }
380 else if (left_of_equal == wxT("short_list_application_ids_for_novice_user_level"))
381 {
382 sTmp = right_of_equal;
383 if (sTmp.Contains( wxT(",") ))
384 sTmp = sTmp.BeforeFirst( wxT(',') );
385 sTmp.Prepend( wxT("open=") );
386 sTmp.Append( wxT(" %s") );
387 entry->Add(sTmp);
388 }
389
390 } // emd of has an equals sign
391 else
392 {
393 // not a comment and not an equals sign
394 if (sTmp.Contains(wxT('/')))
395 {
396 // this is the start of the new mimetype
397 // overwrite any existing data
398 if (! curMimeType.empty())
399 {
400 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc );
401
402 // now get ready for next bit
403 entry = new wxMimeTypeCommands;
404 }
405
406 curMimeType = sTmp.BeforeFirst(wxT(':'));
407 }
408 }
409 } // end of not a comment
410
411 // ignore blank lines
412 nLine++;
413 } // end of while, save any data
414
415 if ( curMimeType.empty() )
416 delete entry;
417 else
418 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc);
419 }
420
421 void wxMimeTypesManagerImpl::LoadGnomeMimeTypesFromMimeFile(const wxString& filename)
422 {
423 wxTextFile textfile(filename);
424 if ( !textfile.Open() )
425 return;
426
427 wxLogTrace(TRACE_MIME,
428 wxT("--- Opened Gnome file %s ---"),
429 filename.c_str());
430
431 // values for the entry being parsed
432 wxString curMimeType, curExtList;
433
434 const wxChar *pc;
435 size_t nLineCount = textfile.GetLineCount();
436 for ( size_t nLine = 0; /* nothing */; nLine++ )
437 {
438 if ( nLine < nLineCount )
439 {
440 pc = textfile[nLine].c_str();
441 if ( *pc == wxT('#') )
442 {
443 // skip comments
444 continue;
445 }
446 }
447 else
448 {
449 // so that we will fall into the "if" below
450 pc = NULL;
451 }
452
453 if ( !pc || !*pc )
454 {
455 // end of the entry
456 if ( !curMimeType.empty() && !curExtList.empty() )
457 {
458 wxLogTrace(TRACE_MIME,
459 wxT("--- At end of Gnome file finding mimetype %s ---"),
460 curMimeType.c_str());
461
462 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
463 }
464
465 if ( !pc )
466 {
467 // the end: this can only happen if nLine == nLineCount
468 break;
469 }
470
471 curExtList.Empty();
472
473 continue;
474 }
475
476 // what do we have here?
477 if ( *pc == wxT('\t') )
478 {
479 // this is a field=value ling
480 pc++; // skip leading TAB
481
482 static const int lenField = 5; // strlen("ext: ")
483 if ( wxStrncmp(pc, wxT("ext: "), lenField) == 0 )
484 {
485 // skip it and take everything left until the end of line
486 curExtList = pc + lenField;
487 }
488 //else: some other field, we don't care
489 }
490 else
491 {
492 // this is the start of the new section
493 wxLogTrace(TRACE_MIME,
494 wxT("--- In Gnome file finding mimetype %s ---"),
495 curMimeType.c_str());
496
497 if (! curMimeType.empty())
498 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
499
500 curMimeType.Empty();
501
502 while ( *pc != wxT(':') && *pc != wxT('\0') )
503 {
504 curMimeType += *pc++;
505 }
506 }
507 }
508 }
509
510
511 void wxMimeTypesManagerImpl::LoadGnomeMimeFilesFromDir(
512 const wxString& dirbase, const wxArrayString& dirs)
513 {
514 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
515 wxT("base directory shouldn't end with a slash") );
516
517 wxString dirname = dirbase;
518 dirname << wxT("/mime-info");
519
520 if ( !wxDir::Exists(dirname) )
521 return;
522
523 wxDir dir(dirname);
524 if ( !dir.IsOpened() )
525 return;
526
527 // we will concatenate it with filename to get the full path below
528 dirname += wxT('/');
529
530 wxString filename;
531 bool cont;
532
533 cont = dir.GetFirst(&filename, wxT("*.mime"), wxDIR_FILES);
534 while ( cont )
535 {
536 LoadGnomeMimeTypesFromMimeFile(dirname + filename);
537
538 cont = dir.GetNext(&filename);
539 }
540
541 cont = dir.GetFirst(&filename, wxT("*.keys"), wxDIR_FILES);
542 while ( cont )
543 {
544 LoadGnomeDataFromKeyFile(dirname + filename, dirs);
545
546 cont = dir.GetNext(&filename);
547 }
548
549 // FIXME: Hack alert: We scan all icons and deduce the
550 // mime-type from the file name.
551 dirname = dirbase;
552 dirname << wxT("/pixmaps/document-icons");
553
554 // these are always empty in this file
555 wxArrayString strExtensions;
556 wxString strDesc;
557
558 if ( !wxDir::Exists(dirname) )
559 {
560 // Just test for default GPE dir also
561 dirname = wxT("/usr/share/gpe/pixmaps/default/filemanager/document-icons");
562
563 if ( !wxDir::Exists(dirname) )
564 return;
565 }
566
567 wxDir dir2( dirname );
568
569 cont = dir2.GetFirst(&filename, wxT("gnome-*.png"), wxDIR_FILES);
570 while ( cont )
571 {
572 wxString mimeType = filename;
573 mimeType.Remove( 0, 6 ); // remove "gnome-"
574 mimeType.Remove( mimeType.Len() - 4, 4 ); // remove ".png"
575 int pos = mimeType.Find( wxT("-") );
576 if (pos != wxNOT_FOUND)
577 {
578 mimeType.SetChar( pos, wxT('/') );
579 wxString iconFile = dirname;
580 iconFile << wxT("/");
581 iconFile << filename;
582 AddToMimeData( mimeType, iconFile, NULL, strExtensions, strDesc, true );
583 }
584
585 cont = dir2.GetNext(&filename);
586 }
587 }
588
589 void wxMimeTypesManagerImpl::GetGnomeMimeInfo(const wxString& sExtraDir)
590 {
591 wxArrayString dirs;
592
593 wxString gnomedir = wxGetenv( wxT("GNOMEDIR") );
594 if (!gnomedir.empty())
595 {
596 gnomedir << wxT("/share");
597 dirs.Add( gnomedir );
598 }
599
600 dirs.Add(wxT("/usr/share"));
601 dirs.Add(wxT("/usr/local/share"));
602
603 gnomedir = wxGetHomeDir();
604 gnomedir << wxT("/.gnome");
605 dirs.Add( gnomedir );
606
607 if (!sExtraDir.empty())
608 dirs.Add( sExtraDir );
609
610 size_t nDirs = dirs.GetCount();
611 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
612 {
613 LoadGnomeMimeFilesFromDir(dirs[nDir], dirs);
614 }
615 }
616
617 // ----------------------------------------------------------------------------
618 // KDE
619 // ----------------------------------------------------------------------------
620
621
622 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
623 // may be found in either of the following locations
624 //
625 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
626 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
627 //
628 // The format of a .kdelnk file is almost the same as the one used by
629 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
630 // value for the entry "Type"
631
632 // kde writing; see http://webcvs.kde.org/cgi-bin/cvsweb.cgi/~checkout~/kdelibs/kio/DESKTOP_ENTRY_STANDARD
633 // for now write to .kdelnk but should eventually do .desktop instead (in preference??)
634
635 bool wxMimeTypesManagerImpl::CheckKDEDirsExist( const wxString &sOK, const wxString &sTest )
636 {
637 if (sTest.empty())
638 {
639 return wxDir::Exists(sOK);
640 }
641 else
642 {
643 wxString sStart = sOK + wxT("/") + sTest.BeforeFirst(wxT('/'));
644 if (!wxDir::Exists(sStart))
645 wxMkdir(sStart);
646 wxString sEnd = sTest.AfterFirst(wxT('/'));
647 return CheckKDEDirsExist(sStart, sEnd);
648 }
649 }
650
651 bool wxMimeTypesManagerImpl::WriteKDEMimeFile(int index, bool delete_index)
652 {
653 wxMimeTextFile appoutfile, mimeoutfile;
654 wxString sHome = wxGetHomeDir();
655 wxString sTmp = wxT(".kde/share/mimelnk/");
656 wxString sMime = m_aTypes[index];
657 CheckKDEDirsExist(sHome, sTmp + sMime.BeforeFirst(wxT('/')) );
658 sTmp = sHome + wxT('/') + sTmp + sMime + wxT(".kdelnk");
659
660 bool bTemp;
661 bool bMimeExists = mimeoutfile.Open(sTmp);
662 if (!bMimeExists)
663 {
664 bTemp = mimeoutfile.Create(sTmp);
665 // some unknown error eg out of disk space
666 if (!bTemp)
667 return false;
668 }
669
670 sTmp = wxT(".kde/share/applnk/");
671 CheckKDEDirsExist(sHome, sTmp + sMime.AfterFirst(wxT('/')) );
672 sTmp = sHome + wxT('/') + sTmp + sMime.AfterFirst(wxT('/')) + wxT(".kdelnk");
673
674 bool bAppExists;
675 bAppExists = appoutfile.Open(sTmp);
676 if (!bAppExists)
677 {
678 bTemp = appoutfile.Create(sTmp);
679 // some unknown error eg out of disk space
680 if (!bTemp)
681 return false;
682 }
683
684 // fixed data; write if new file
685 if (!bMimeExists)
686 {
687 mimeoutfile.AddLine(wxT("#KDE Config File"));
688 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
689 mimeoutfile.AddLine(wxT("Version=1.0"));
690 mimeoutfile.AddLine(wxT("Type=MimeType"));
691 mimeoutfile.AddLine(wxT("MimeType=") + sMime);
692 }
693
694 if (!bAppExists)
695 {
696 mimeoutfile.AddLine(wxT("#KDE Config File"));
697 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
698 appoutfile.AddLine(wxT("Version=1.0"));
699 appoutfile.AddLine(wxT("Type=Application"));
700 appoutfile.AddLine(wxT("MimeType=") + sMime + wxT(';'));
701 }
702
703 // variable data
704 // ignore locale
705 mimeoutfile.CommentLine(wxT("Comment="));
706 if (!delete_index)
707 mimeoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
708 appoutfile.CommentLine(wxT("Name="));
709 if (!delete_index)
710 appoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
711
712 sTmp = m_aIcons[index];
713 // we can either give the full path, or the shortfilename if its in
714 // one of the directories we search
715 mimeoutfile.CommentLine(wxT("Icon=") );
716 if (!delete_index)
717 mimeoutfile.AddLine(wxT("Icon=") + sTmp );
718 appoutfile.CommentLine(wxT("Icon=") );
719 if (!delete_index)
720 appoutfile.AddLine(wxT("Icon=") + sTmp );
721
722 sTmp = wxT(" ") + m_aExtensions[index];
723
724 wxStringTokenizer tokenizer(sTmp, wxT(" "));
725 sTmp = wxT("Patterns=");
726 mimeoutfile.CommentLine(sTmp);
727 while ( tokenizer.HasMoreTokens() )
728 {
729 // holds an extension; need to change it to *.ext;
730 wxString e = wxT("*.") + tokenizer.GetNextToken() + wxT(";");
731 sTmp = sTmp + e;
732 }
733
734 if (!delete_index)
735 mimeoutfile.AddLine(sTmp);
736
737 wxMimeTypeCommands * entries = m_aEntries[index];
738 // if we don't find open just have an empty string ... FIX this
739 sTmp = entries->GetCommandForVerb(wxT("open"));
740 sTmp.Replace( wxT("%s"), wxT("%f") );
741
742 mimeoutfile.CommentLine(wxT("DefaultApp=") );
743 if (!delete_index)
744 mimeoutfile.AddLine(wxT("DefaultApp=") + sTmp);
745
746 sTmp.Replace( wxT("%f"), wxT("") );
747 appoutfile.CommentLine(wxT("Exec="));
748 if (!delete_index)
749 appoutfile.AddLine(wxT("Exec=") + sTmp);
750
751 if (entries->GetCount() > 1)
752 {
753 //other actions as well as open
754 }
755
756 bTemp = false;
757 if (mimeoutfile.Write())
758 bTemp = true;
759 mimeoutfile.Close();
760 if (appoutfile.Write())
761 bTemp = true;
762 appoutfile.Close();
763
764 return bTemp;
765 }
766
767 void wxMimeTypesManagerImpl::LoadKDELinksForMimeSubtype(const wxString& dirbase,
768 const wxString& subdir,
769 const wxString& filename,
770 const wxArrayString& icondirs)
771 {
772 wxMimeTextFile file;
773 if ( !file.Open(dirbase + filename) )
774 return;
775
776 wxLogTrace(TRACE_MIME, wxT("loading KDE file %s"),
777 (dirbase + filename).c_str());
778
779 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
780 wxArrayString sExts;
781 wxString mimetype, mime_desc, strIcon;
782
783 int nIndex = file.pIndexOf( wxT("MimeType=") );
784 if (nIndex == wxNOT_FOUND)
785 {
786 // construct mimetype from the directory name and the basename of the
787 // file (it always has .kdelnk extension)
788 mimetype << subdir << wxT('/') << filename.BeforeLast( wxT('.') );
789 }
790 else
791 mimetype = file.GetCmd(nIndex);
792
793 // first find the description string: it is the value in either "Comment="
794 // line or "Comment[<locale_name>]=" one
795 nIndex = wxNOT_FOUND;
796
797 wxString comment;
798
799 #if wxUSE_INTL
800 wxLocale *locale = wxGetLocale();
801 if ( locale )
802 {
803 // try "Comment[locale name]" first
804 comment << wxT("Comment[") + locale->GetName() + wxT("]=");
805 nIndex = file.pIndexOf(comment);
806 }
807 #endif
808
809 if ( nIndex == wxNOT_FOUND )
810 {
811 comment = wxT("Comment=");
812 nIndex = file.pIndexOf(comment);
813 }
814
815 if ( nIndex != wxNOT_FOUND )
816 mime_desc = file.GetCmd(nIndex);
817 //else: no description
818
819 // next find the extensions
820 wxString mime_extension;
821
822 nIndex = file.pIndexOf(wxT("Patterns="));
823 if ( nIndex != wxNOT_FOUND )
824 {
825 wxString exts = file.GetCmd(nIndex);
826
827 wxStringTokenizer tokenizer(exts, wxT(";"));
828 while ( tokenizer.HasMoreTokens() )
829 {
830 wxString e = tokenizer.GetNextToken();
831
832 // don't support too difficult patterns
833 if ( e.Left(2) != wxT("*.") )
834 continue;
835
836 if ( !mime_extension.empty() )
837 {
838 // separate from the previous ext
839 mime_extension << wxT(' ');
840 }
841
842 mime_extension << e.Mid(2);
843 }
844 }
845
846 sExts.Add(mime_extension);
847
848 // ok, now we can take care of icon:
849
850 nIndex = file.pIndexOf(wxT("Icon="));
851 if ( nIndex != wxNOT_FOUND )
852 {
853 strIcon = file.GetCmd(nIndex);
854
855 wxLogTrace(TRACE_MIME, wxT(" icon %s"), strIcon.c_str());
856
857 // it could be the real path, but more often a short name
858 if (!wxFileExists(strIcon))
859 {
860 // icon is just the short name
861 if ( !strIcon.empty() )
862 {
863 // we must check if the file exists because it may be stored
864 // in many locations, at least ~/.kde and $KDEDIR
865 size_t nDir, nDirs = icondirs.GetCount();
866 for ( nDir = 0; nDir < nDirs; nDir++ )
867 {
868 wxFileName fnameIcon( strIcon );
869 wxFileName fname( icondirs[nDir], fnameIcon.GetName() );
870 fname.SetExt( wxT("png") );
871 if (fname.FileExists())
872 {
873 strIcon = fname.GetFullPath();
874 wxLogTrace(TRACE_MIME, wxT(" iconfile %s"), strIcon.c_str());
875 break;
876 }
877 }
878 }
879 }
880 }
881
882 // now look for lines which know about the application
883 // exec= or DefaultApp=
884
885 nIndex = file.pIndexOf(wxT("DefaultApp"));
886
887 if ( nIndex == wxNOT_FOUND )
888 {
889 // no entry try exec
890 nIndex = file.pIndexOf(wxT("Exec"));
891 }
892
893 if ( nIndex != wxNOT_FOUND )
894 {
895 // we expect %f; others including %F and %U and %u are possible
896 wxString sTmp = file.GetCmd(nIndex);
897 if (0 == sTmp.Replace( wxT("%f"), wxT("%s") ))
898 sTmp = sTmp + wxT(" %s");
899 entry->AddOrReplaceVerb(wxString(wxT("open")), sTmp );
900 }
901
902 AddToMimeData(mimetype, strIcon, entry, sExts, mime_desc);
903 }
904
905 void wxMimeTypesManagerImpl::LoadKDELinksForMimeType(const wxString& dirbase,
906 const wxString& subdir,
907 const wxArrayString& icondirs)
908 {
909 wxString dirname = dirbase;
910 dirname += subdir;
911 wxDir dir(dirname);
912 if ( !dir.IsOpened() )
913 return;
914
915 wxLogTrace(TRACE_MIME, wxT("--- Loading from KDE directory %s ---"),
916 dirname.c_str());
917
918 dirname += wxT('/');
919
920 wxString filename;
921 bool cont = dir.GetFirst(&filename, wxT("*.kdelnk"), wxDIR_FILES);
922 while ( cont )
923 {
924 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
925
926 cont = dir.GetNext(&filename);
927 }
928
929 // new standard for Gnome and KDE
930 cont = dir.GetFirst(&filename, wxT("*.desktop"), wxDIR_FILES);
931 while ( cont )
932 {
933 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
934
935 cont = dir.GetNext(&filename);
936 }
937 }
938
939 void wxMimeTypesManagerImpl::LoadKDELinkFilesFromDir(const wxString& dirbase,
940 const wxArrayString& icondirs)
941 {
942 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
943 wxT("base directory shouldn't end with a slash") );
944
945 wxString dirname = dirbase;
946 dirname << wxT("/mimelnk");
947
948 if ( !wxDir::Exists(dirname) )
949 return;
950
951 wxDir dir(dirname);
952 if ( !dir.IsOpened() )
953 return;
954
955 // we will concatenate it with dir name to get the full path below
956 dirname += wxT('/');
957
958 wxString subdir;
959 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
960 while ( cont )
961 {
962 LoadKDELinksForMimeType(dirname, subdir, icondirs);
963
964 cont = dir.GetNext(&subdir);
965 }
966 }
967
968 void wxMimeTypesManagerImpl::GetKDEMimeInfo(const wxString& sExtraDir)
969 {
970 wxArrayString dirs;
971 wxArrayString icondirs;
972
973 // FIXME: This code is heavily broken. There are three bugs in it:
974 // 1) it uses only KDEDIR, which is deprecated, instead of using
975 // list of paths from KDEDIRS and using KDEDIR only if KDEDIRS
976 // is not set
977 // 2) it doesn't look into ~/.kde/share/config/kdeglobals where
978 // user's settings are stored and thus *ignores* user's settings
979 // instead of respecting them
980 // 3) it "tries to guess KDEDIR" and "tries a few likely theme
981 // names", both of which is completely arbitrary; instead, the
982 // code should give up if KDEDIR(S) is not set and/or the icon
983 // theme cannot be determined, because it means that the user is
984 // not using KDE (and thus is not interested in KDE icons anyway)
985
986 // the variable $KDEDIR is set when KDE is running
987 wxString kdedir = wxGetenv( wxT("KDEDIR") );
988
989 if (!kdedir.empty())
990 {
991 // $(KDEDIR)/share/config/kdeglobals holds info
992 // the current icons theme
993 wxFileName configFile( kdedir, wxEmptyString );
994 configFile.AppendDir( wxT("share") );
995 configFile.AppendDir( wxT("config") );
996 configFile.SetName( wxT("kdeglobals") );
997
998 wxTextFile config;
999 if (configFile.FileExists() && config.Open(configFile.GetFullPath()))
1000 {
1001 // $(KDEDIR)/share/config -> $(KDEDIR)/share
1002 configFile.RemoveDir( configFile.GetDirCount() - 1 );
1003 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1004 configFile.AppendDir( wxT("icons") );
1005
1006 // Check for entry
1007 wxString theme(wxT("default.kde"));
1008 size_t cnt = config.GetLineCount();
1009 for (size_t i = 0; i < cnt; i++)
1010 {
1011 if (config[i].StartsWith(wxT("Theme="), &theme/*rest*/))
1012 break;
1013 }
1014
1015 configFile.AppendDir(theme);
1016 }
1017 else
1018 {
1019 // $(KDEDIR)/share/config -> $(KDEDIR)/share
1020 configFile.RemoveDir( configFile.GetDirCount() - 1 );
1021
1022 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1023 configFile.AppendDir( wxT("icons") );
1024
1025 // $(KDEDIR)/share/icons -> $(KDEDIR)/share/icons/default.kde
1026 configFile.AppendDir( wxT("default.kde") );
1027 }
1028
1029 configFile.SetName( wxEmptyString );
1030 configFile.AppendDir( wxT("32x32") );
1031 configFile.AppendDir( wxT("mimetypes") );
1032
1033 // Just try a few likely icons theme names
1034
1035 int pos = configFile.GetDirCount() - 3;
1036
1037 if (!wxDir::Exists(configFile.GetPath()))
1038 {
1039 configFile.RemoveDir( pos );
1040 configFile.InsertDir( pos, wxT("default.kde") );
1041 }
1042
1043 if (!wxDir::Exists(configFile.GetPath()))
1044 {
1045 configFile.RemoveDir( pos );
1046 configFile.InsertDir( pos, wxT("default") );
1047 }
1048
1049 if (!wxDir::Exists(configFile.GetPath()))
1050 {
1051 configFile.RemoveDir( pos );
1052 configFile.InsertDir( pos, wxT("crystalsvg") );
1053 }
1054
1055 if (!wxDir::Exists(configFile.GetPath()))
1056 {
1057 configFile.RemoveDir( pos );
1058 configFile.InsertDir( pos, wxT("crystal") );
1059 }
1060
1061 if (wxDir::Exists(configFile.GetPath()))
1062 icondirs.Add( configFile.GetFullPath() );
1063 }
1064
1065 // settings in ~/.kde have maximal priority
1066 dirs.Add(wxGetHomeDir() + wxT("/.kde/share"));
1067 icondirs.Add(wxGetHomeDir() + wxT("/.kde/share/icons/"));
1068
1069 if (kdedir)
1070 {
1071 dirs.Add( wxString(kdedir) + wxT("/share") );
1072 icondirs.Add( wxString(kdedir) + wxT("/share/icons/") );
1073 }
1074 else
1075 {
1076 // try to guess KDEDIR
1077 dirs.Add(wxT("/usr/share"));
1078 dirs.Add(wxT("/opt/kde/share"));
1079 icondirs.Add(wxT("/usr/share/icons/"));
1080 icondirs.Add(wxT("/usr/X11R6/share/icons/")); // Debian/Corel linux
1081 icondirs.Add(wxT("/opt/kde/share/icons/"));
1082 }
1083
1084 if (!sExtraDir.empty())
1085 dirs.Add(sExtraDir);
1086 icondirs.Add(sExtraDir + wxT("/icons"));
1087
1088 size_t nDirs = dirs.GetCount();
1089 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1090 {
1091 LoadKDELinkFilesFromDir(dirs[nDir], icondirs);
1092 }
1093 }
1094
1095 // ----------------------------------------------------------------------------
1096 // wxFileTypeImpl (Unix)
1097 // ----------------------------------------------------------------------------
1098
1099 wxString wxFileTypeImpl::GetExpandedCommand(const wxString & verb, const wxFileType::MessageParameters& params) const
1100 {
1101 wxString sTmp;
1102 size_t i = 0;
1103 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1104 {
1105 sTmp = m_manager->GetCommand( verb, m_index[i] );
1106 i++;
1107 }
1108
1109 return wxFileType::ExpandCommand(sTmp, params);
1110 }
1111
1112 bool wxFileTypeImpl::GetIcon(wxIconLocation *iconLoc) const
1113 {
1114 wxString sTmp;
1115 size_t i = 0;
1116 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1117 {
1118 sTmp = m_manager->m_aIcons[m_index[i]];
1119 i++;
1120 }
1121
1122 if ( sTmp.empty() )
1123 return false;
1124
1125 if ( iconLoc )
1126 {
1127 iconLoc->SetFileName(sTmp);
1128 }
1129
1130 return true;
1131 }
1132
1133 bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
1134 {
1135 mimeTypes.Clear();
1136 for (size_t i = 0; i < m_index.GetCount(); i++)
1137 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
1138
1139 return true;
1140 }
1141
1142 size_t wxFileTypeImpl::GetAllCommands(wxArrayString *verbs,
1143 wxArrayString *commands,
1144 const wxFileType::MessageParameters& params) const
1145 {
1146 wxString vrb, cmd, sTmp;
1147 size_t count = 0;
1148 wxMimeTypeCommands * sPairs;
1149
1150 // verbs and commands have been cleared already in mimecmn.cpp...
1151 // if we find no entries in the exact match, try the inexact match
1152 for (size_t n = 0; ((count == 0) && (n < m_index.GetCount())); n++)
1153 {
1154 // list of verb = command pairs for this mimetype
1155 sPairs = m_manager->m_aEntries [m_index[n]];
1156 size_t i;
1157 for ( i = 0; i < sPairs->GetCount(); i++ )
1158 {
1159 vrb = sPairs->GetVerb(i);
1160 // some gnome entries have "." inside
1161 vrb = vrb.AfterLast(wxT('.'));
1162 cmd = sPairs->GetCmd(i);
1163 if (! cmd.empty() )
1164 {
1165 cmd = wxFileType::ExpandCommand(cmd, params);
1166 count++;
1167 if ( vrb.IsSameAs(wxT("open")))
1168 {
1169 if ( verbs )
1170 verbs->Insert(vrb, 0u);
1171 if ( commands )
1172 commands ->Insert(cmd, 0u);
1173 }
1174 else
1175 {
1176 if ( verbs )
1177 verbs->Add(vrb);
1178 if ( commands )
1179 commands->Add(cmd);
1180 }
1181 }
1182 }
1183 }
1184
1185 return count;
1186 }
1187
1188 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
1189 {
1190 wxString strExtensions = m_manager->GetExtension(m_index[0]);
1191 extensions.Empty();
1192
1193 // one extension in the space or comma-delimited list
1194 wxString strExt;
1195 for ( const wxChar *p = strExtensions; /* nothing */; p++ )
1196 {
1197 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') )
1198 {
1199 if ( !strExt.empty() )
1200 {
1201 extensions.Add(strExt);
1202 strExt.Empty();
1203 }
1204 //else: repeated spaces
1205 // (shouldn't happen, but it's not that important if it does happen)
1206
1207 if ( *p == wxT('\0') )
1208 break;
1209 }
1210 else if ( *p == wxT('.') )
1211 {
1212 // remove the dot from extension (but only if it's the first char)
1213 if ( !strExt.empty() )
1214 {
1215 strExt += wxT('.');
1216 }
1217 //else: no, don't append it
1218 }
1219 else
1220 {
1221 strExt += *p;
1222 }
1223 }
1224
1225 return true;
1226 }
1227
1228 // set an arbitrary command:
1229 // could adjust the code to ask confirmation if it already exists and
1230 // overwriteprompt is true, but this is currently ignored as *Associate* has
1231 // no overwrite prompt
1232 bool
1233 wxFileTypeImpl::SetCommand(const wxString& cmd,
1234 const wxString& verb,
1235 bool WXUNUSED(overwriteprompt))
1236 {
1237 wxArrayString strExtensions;
1238 wxString strDesc, strIcon;
1239
1240 wxArrayString strTypes;
1241 GetMimeTypes(strTypes);
1242 if ( strTypes.IsEmpty() )
1243 return false;
1244
1245 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1246 entry->Add(verb + wxT("=") + cmd + wxT(" %s "));
1247
1248 bool ok = true;
1249 for ( size_t i = 0; i < strTypes.GetCount(); i++ )
1250 {
1251 if (!m_manager->DoAssociation(strTypes[i], strIcon, entry, strExtensions, strDesc))
1252 ok = false;
1253 }
1254
1255 return ok;
1256 }
1257
1258 // ignore index on the grouds that we only have one icon in a Unix file
1259 bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int WXUNUSED(index))
1260 {
1261 if (strIcon.empty())
1262 return false;
1263
1264 wxArrayString strExtensions;
1265 wxString strDesc;
1266
1267 wxArrayString strTypes;
1268 GetMimeTypes(strTypes);
1269 if ( strTypes.IsEmpty() )
1270 return false;
1271
1272 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1273 bool ok = true;
1274 for ( size_t i = 0; i < strTypes.GetCount(); i++ )
1275 {
1276 if ( !m_manager->DoAssociation
1277 (
1278 strTypes[i],
1279 strIcon,
1280 entry,
1281 strExtensions,
1282 strDesc
1283 ) )
1284 {
1285 ok = false;
1286 }
1287 }
1288
1289 return ok;
1290 }
1291
1292 // ----------------------------------------------------------------------------
1293 // wxMimeTypesManagerImpl (Unix)
1294 // ----------------------------------------------------------------------------
1295
1296 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1297 {
1298 m_initialized = false;
1299 m_mailcapStylesInited = 0;
1300 }
1301
1302 void wxMimeTypesManagerImpl::InitIfNeeded()
1303 {
1304 if ( !m_initialized )
1305 {
1306 // set the flag first to prevent recursion
1307 m_initialized = true;
1308
1309 wxString wm = wxGetenv( wxT("WINDOWMANAGER") );
1310
1311 if (wm.Find( wxT("kde") ) != wxNOT_FOUND)
1312 Initialize( wxMAILCAP_KDE );
1313 else if (wm.Find( wxT("gnome") ) != wxNOT_FOUND)
1314 Initialize( wxMAILCAP_GNOME );
1315 else
1316 Initialize();
1317 }
1318 }
1319
1320 // read system and user mailcaps and other files
1321 void wxMimeTypesManagerImpl::Initialize(int mailcapStyles,
1322 const wxString& sExtraDir)
1323 {
1324 // read mimecap amd mime.types
1325 if ( (mailcapStyles & wxMAILCAP_NETSCAPE) ||
1326 (mailcapStyles & wxMAILCAP_STANDARD) )
1327 GetMimeInfo(sExtraDir);
1328
1329 // read GNOME tables
1330 if (mailcapStyles & wxMAILCAP_GNOME)
1331 GetGnomeMimeInfo(sExtraDir);
1332
1333 // read KDE tables
1334 if (mailcapStyles & wxMAILCAP_KDE)
1335 GetKDEMimeInfo(sExtraDir);
1336
1337 m_mailcapStylesInited |= mailcapStyles;
1338 }
1339
1340 // clear data so you can read another group of WM files
1341 void wxMimeTypesManagerImpl::ClearData()
1342 {
1343 m_aTypes.Clear();
1344 m_aIcons.Clear();
1345 m_aExtensions.Clear();
1346 m_aDescriptions.Clear();
1347
1348 WX_CLEAR_ARRAY(m_aEntries);
1349 m_aEntries.Empty();
1350
1351 m_mailcapStylesInited = 0;
1352 }
1353
1354 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1355 {
1356 ClearData();
1357 }
1358
1359 void wxMimeTypesManagerImpl::GetMimeInfo(const wxString& sExtraDir)
1360 {
1361 // read this for netscape or Metamail formats
1362
1363 // directories where we look for mailcap and mime.types by default
1364 // used by netscape and pine and other mailers, using 2 different formats!
1365
1366 // (taken from metamail(1) sources)
1367 //
1368 // although RFC 1524 specifies the search path of
1369 // /etc/:/usr/etc:/usr/local/etc only, it doesn't hurt to search in more
1370 // places - OTOH, the RFC also says that this path can be changed with
1371 // MAILCAPS environment variable (containing the colon separated full
1372 // filenames to try) which is not done yet (TODO?)
1373
1374 wxString strHome = wxGetenv(wxT("HOME"));
1375
1376 wxArrayString dirs;
1377 dirs.Add( strHome + wxT("/.") );
1378 dirs.Add( wxT("/etc/") );
1379 dirs.Add( wxT("/usr/etc/") );
1380 dirs.Add( wxT("/usr/local/etc/") );
1381 dirs.Add( wxT("/etc/mail/") );
1382 dirs.Add( wxT("/usr/public/lib/") );
1383 if (!sExtraDir.empty())
1384 dirs.Add( sExtraDir + wxT("/") );
1385
1386 size_t nDirs = dirs.GetCount();
1387 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1388 {
1389 wxString file = dirs[nDir] + wxT("mailcap");
1390 if ( wxFile::Exists(file) )
1391 {
1392 ReadMailcap(file);
1393 }
1394
1395 file = dirs[nDir] + wxT("mime.types");
1396 if ( wxFile::Exists(file) )
1397 {
1398 ReadMimeTypes(file);
1399 }
1400 }
1401 }
1402
1403 bool wxMimeTypesManagerImpl::WriteToMimeTypes(int index, bool delete_index)
1404 {
1405 // check we have the right manager
1406 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD) )
1407 return false;
1408
1409 bool bTemp;
1410 wxString strHome = wxGetenv(wxT("HOME"));
1411
1412 // and now the users mailcap
1413 wxString strUserMailcap = strHome + wxT("/.mime.types");
1414
1415 wxMimeTextFile file;
1416 if ( wxFile::Exists(strUserMailcap) )
1417 {
1418 bTemp = file.Open(strUserMailcap);
1419 }
1420 else
1421 {
1422 if (delete_index)
1423 return false;
1424
1425 bTemp = file.Create(strUserMailcap);
1426 }
1427
1428 if (bTemp)
1429 {
1430 int nIndex;
1431 // test for netscape's header and return false if its found
1432 nIndex = file.pIndexOf(wxT("#--Netscape"));
1433 if (nIndex != wxNOT_FOUND)
1434 {
1435 wxASSERT_MSG(false,wxT("Error in .mime.types \nTrying to mix Netscape and Metamail formats\nFile not modiifed"));
1436 return false;
1437 }
1438
1439 // write it in alternative format
1440 // get rid of unwanted entries
1441 wxString strType = m_aTypes[index];
1442 nIndex = file.pIndexOf(strType);
1443
1444 // get rid of all the unwanted entries...
1445 if (nIndex != wxNOT_FOUND)
1446 file.CommentLine(nIndex);
1447
1448 if (!delete_index)
1449 {
1450 // add the new entries in
1451 wxString sTmp = strType.Append( wxT(' '), 40 - strType.Len() );
1452 sTmp = sTmp + m_aExtensions[index];
1453 file.AddLine(sTmp);
1454 }
1455
1456 bTemp = file.Write();
1457 file.Close();
1458 }
1459
1460 return bTemp;
1461 }
1462
1463 bool wxMimeTypesManagerImpl::WriteToNSMimeTypes(int index, bool delete_index)
1464 {
1465 //check we have the right managers
1466 if (! ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) )
1467 return false;
1468
1469 bool bTemp;
1470 wxString strHome = wxGetenv(wxT("HOME"));
1471
1472 // and now the users mailcap
1473 wxString strUserMailcap = strHome + wxT("/.mime.types");
1474
1475 wxMimeTextFile file;
1476 if ( wxFile::Exists(strUserMailcap) )
1477 {
1478 bTemp = file.Open(strUserMailcap);
1479 }
1480 else
1481 {
1482 if (delete_index)
1483 return false;
1484
1485 bTemp = file.Create(strUserMailcap);
1486 }
1487
1488 if (bTemp)
1489 {
1490 // write it in the format that Netscape uses
1491 int nIndex;
1492 // test for netscape's header and insert if required...
1493 // this is a comment so use true
1494 nIndex = file.pIndexOf(wxT("#--Netscape"), true);
1495 if (nIndex == wxNOT_FOUND)
1496 {
1497 // either empty file or metamail format
1498 // at present we can't cope with mixed formats, so exit to preseve
1499 // metamail entreies
1500 if (file.GetLineCount() > 0)
1501 {
1502 wxASSERT_MSG(false, wxT(".mime.types File not in Netscape format\nNo entries written to\n.mime.types or to .mailcap"));
1503 return false;
1504 }
1505
1506 file.InsertLine(wxT( "#--Netscape Communications Corporation MIME Information" ), 0);
1507 nIndex = 0;
1508 }
1509
1510 wxString strType = wxT("type=") + m_aTypes[index];
1511 nIndex = file.pIndexOf(strType);
1512
1513 // get rid of all the unwanted entries...
1514 if (nIndex != wxNOT_FOUND)
1515 {
1516 wxString sOld = file[nIndex];
1517 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1518 {
1519 file.CommentLine(nIndex);
1520 sOld = file[nIndex];
1521
1522 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mime.types line '%d %s' ---"), nIndex, sOld.c_str());
1523
1524 nIndex++;
1525 }
1526
1527 if (nIndex < (int) file.GetLineCount())
1528 file.CommentLine(nIndex);
1529 }
1530 else
1531 nIndex = (int) file.GetLineCount();
1532
1533 wxString sTmp = strType + wxT(" \\");
1534 if (!delete_index)
1535 file.InsertLine(sTmp, nIndex);
1536
1537 if ( ! m_aDescriptions.Item(index).empty() )
1538 {
1539 sTmp = wxT("desc=\"") + m_aDescriptions[index]+ wxT("\" \\"); //.trim ??
1540 if (!delete_index)
1541 {
1542 nIndex++;
1543 file.InsertLine(sTmp, nIndex);
1544 }
1545 }
1546
1547 wxString sExts = m_aExtensions.Item(index);
1548 sTmp = wxT("exts=\"") + sExts.Trim(false).Trim() + wxT("\"");
1549 if (!delete_index)
1550 {
1551 nIndex++;
1552 file.InsertLine(sTmp, nIndex);
1553 }
1554
1555 bTemp = file.Write();
1556 file.Close();
1557 }
1558
1559 return bTemp;
1560 }
1561
1562 bool wxMimeTypesManagerImpl::WriteToMailCap(int index, bool delete_index)
1563 {
1564 //check we have the right managers
1565 if ( !( ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) ||
1566 ( m_mailcapStylesInited & wxMAILCAP_STANDARD) ) )
1567 return false;
1568
1569 bool bTemp = false;
1570 wxString strHome = wxGetenv(wxT("HOME"));
1571
1572 // and now the users mailcap
1573 wxString strUserMailcap = strHome + wxT("/.mailcap");
1574
1575 wxMimeTextFile file;
1576 if ( wxFile::Exists(strUserMailcap) )
1577 {
1578 bTemp = file.Open(strUserMailcap);
1579 }
1580 else
1581 {
1582 if (delete_index)
1583 return false;
1584
1585 bTemp = file.Create(strUserMailcap);
1586 }
1587
1588 if (bTemp)
1589 {
1590 // now got a file we can write to ....
1591 wxMimeTypeCommands * entries = m_aEntries[index];
1592 size_t iOpen;
1593 wxString sCmd = entries->GetCommandForVerb(wxT("open"), &iOpen);
1594 wxString sTmp;
1595
1596 sTmp = m_aTypes[index];
1597 wxString sOld;
1598 int nIndex = file.pIndexOf(sTmp);
1599
1600 // get rid of all the unwanted entries...
1601 if (nIndex == wxNOT_FOUND)
1602 {
1603 nIndex = (int) file.GetLineCount();
1604 }
1605 else
1606 {
1607 sOld = file[nIndex];
1608 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mailcap line '%d' ---"), nIndex);
1609
1610 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1611 {
1612 file.CommentLine(nIndex);
1613 if (nIndex < (int) file.GetLineCount())
1614 sOld = sOld + file[nIndex];
1615 }
1616
1617 if (nIndex < (int)
1618 file.GetLineCount()) file.CommentLine(nIndex);
1619 }
1620
1621 sTmp = sTmp + wxT(";") + sCmd; //includes wxT(" %s ");
1622
1623 // write it in the format that Netscape uses (default)
1624 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD ) )
1625 {
1626 if (! delete_index)
1627 file.InsertLine(sTmp, nIndex);
1628 nIndex++;
1629 }
1630 else
1631 {
1632 // write extended format
1633
1634 // TODO - FIX this code:
1635 // ii) lost entries
1636 // sOld holds all the entries, but our data store only has some
1637 // eg test= is not stored
1638
1639 // so far we have written the mimetype and command out
1640 wxStringTokenizer sT(sOld, wxT(";\\"));
1641 if (sT.CountTokens() > 2)
1642 {
1643 // first one mimetype; second one command, rest unknown...
1644 wxString s;
1645 s = sT.GetNextToken();
1646 s = sT.GetNextToken();
1647
1648 // first unknown
1649 s = sT.GetNextToken();
1650 while ( ! s.empty() )
1651 {
1652 bool bKnownToken = false;
1653 if (s.Contains(wxT("description=")))
1654 bKnownToken = true;
1655 if (s.Contains(wxT("x11-bitmap=")))
1656 bKnownToken = true;
1657
1658 size_t i;
1659 for (i=0; i < entries->GetCount(); i++)
1660 {
1661 if (s.Contains(entries->GetVerb(i)))
1662 bKnownToken = true;
1663 }
1664
1665 if (!bKnownToken)
1666 {
1667 sTmp = sTmp + wxT("; \\");
1668 file.InsertLine(sTmp, nIndex);
1669 sTmp = s;
1670 }
1671
1672 s = sT.GetNextToken();
1673 }
1674 }
1675
1676 if (! m_aDescriptions[index].empty() )
1677 {
1678 sTmp = sTmp + wxT("; \\");
1679 file.InsertLine(sTmp, nIndex);
1680 nIndex++;
1681 sTmp = wxT(" description=\"") + m_aDescriptions[index] + wxT("\"");
1682 }
1683
1684 if (! m_aIcons[index].empty() )
1685 {
1686 sTmp = sTmp + wxT("; \\");
1687 file.InsertLine(sTmp, nIndex);
1688 nIndex++;
1689 sTmp = wxT(" x11-bitmap=\"") + m_aIcons[index] + wxT("\"");
1690 }
1691
1692 if ( entries->GetCount() > 1 )
1693 {
1694 size_t i;
1695 for (i=0; i < entries->GetCount(); i++)
1696 if ( i != iOpen )
1697 {
1698 sTmp = sTmp + wxT("; \\");
1699 file.InsertLine(sTmp, nIndex);
1700 nIndex++;
1701 sTmp = wxT(" ") + entries->GetVerbCmd(i);
1702 }
1703 }
1704
1705 file.InsertLine(sTmp, nIndex);
1706 nIndex++;
1707 }
1708
1709 bTemp = file.Write();
1710 file.Close();
1711 }
1712
1713 return bTemp;
1714 }
1715
1716 wxFileType * wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
1717 {
1718 InitIfNeeded();
1719
1720 wxString strType = ftInfo.GetMimeType();
1721 wxString strDesc = ftInfo.GetDescription();
1722 wxString strIcon = ftInfo.GetIconFile();
1723
1724 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1725
1726 if ( ! ftInfo.GetOpenCommand().empty())
1727 entry->Add(wxT("open=") + ftInfo.GetOpenCommand() + wxT(" %s "));
1728 if ( ! ftInfo.GetPrintCommand().empty())
1729 entry->Add(wxT("print=") + ftInfo.GetPrintCommand() + wxT(" %s "));
1730
1731 // now find where these extensions are in the data store and remove them
1732 wxArrayString sA_Exts = ftInfo.GetExtensions();
1733 wxString sExt, sExtStore;
1734 size_t i, nIndex;
1735 for (i=0; i < sA_Exts.GetCount(); i++)
1736 {
1737 sExt = sA_Exts.Item(i);
1738
1739 // clean up to just a space before and after
1740 sExt.Trim().Trim(false);
1741 sExt = wxT(' ') + sExt + wxT(' ');
1742 for (nIndex = 0; nIndex < m_aExtensions.GetCount(); nIndex++)
1743 {
1744 sExtStore = m_aExtensions.Item(nIndex);
1745 if (sExtStore.Replace(sExt, wxT(" ") ) > 0)
1746 m_aExtensions.Item(nIndex) = sExtStore;
1747 }
1748 }
1749
1750 if ( !DoAssociation(strType, strIcon, entry, sA_Exts, strDesc) )
1751 return NULL;
1752
1753 return GetFileTypeFromMimeType(strType);
1754 }
1755
1756 bool wxMimeTypesManagerImpl::DoAssociation(const wxString& strType,
1757 const wxString& strIcon,
1758 wxMimeTypeCommands *entry,
1759 const wxArrayString& strExtensions,
1760 const wxString& strDesc)
1761 {
1762 int nIndex = AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
1763
1764 if ( nIndex == wxNOT_FOUND )
1765 return false;
1766
1767 return WriteMimeInfo(nIndex, false);
1768 }
1769
1770 bool wxMimeTypesManagerImpl::WriteMimeInfo(int nIndex, bool delete_mime )
1771 {
1772 bool ok = true;
1773
1774 if ( m_mailcapStylesInited & wxMAILCAP_STANDARD )
1775 {
1776 // write in metamail format;
1777 if (WriteToMimeTypes(nIndex, delete_mime) )
1778 if ( WriteToMailCap(nIndex, delete_mime) )
1779 ok = false;
1780 }
1781
1782 if ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE )
1783 {
1784 // write in netsacpe format;
1785 if (WriteToNSMimeTypes(nIndex, delete_mime) )
1786 if ( WriteToMailCap(nIndex, delete_mime) )
1787 ok = false;
1788 }
1789
1790 // Don't write GNOME files here as this is not
1791 // allowed and simply doesn't work
1792
1793 if (m_mailcapStylesInited & wxMAILCAP_KDE)
1794 {
1795 // write in KDE format;
1796 if (WriteKDEMimeFile(nIndex, delete_mime) )
1797 ok = false;
1798 }
1799
1800 return ok;
1801 }
1802
1803 int wxMimeTypesManagerImpl::AddToMimeData(const wxString& strType,
1804 const wxString& strIcon,
1805 wxMimeTypeCommands *entry,
1806 const wxArrayString& strExtensions,
1807 const wxString& strDesc,
1808 bool replaceExisting)
1809 {
1810 InitIfNeeded();
1811
1812 // ensure mimetype is always lower case
1813 wxString mimeType = strType.Lower();
1814
1815 // is this a known MIME type?
1816 int nIndex = m_aTypes.Index(mimeType);
1817 if ( nIndex == wxNOT_FOUND )
1818 {
1819 // new file type
1820 m_aTypes.Add(mimeType);
1821 m_aIcons.Add(strIcon);
1822 m_aEntries.Add(entry ? entry : new wxMimeTypeCommands);
1823
1824 // change nIndex so we can use it below to add the extensions
1825 m_aExtensions.Add(wxEmptyString);
1826 nIndex = m_aExtensions.size() - 1;
1827
1828 m_aDescriptions.Add(strDesc);
1829 }
1830 else // yes, we already have it
1831 {
1832 if ( replaceExisting )
1833 {
1834 // if new description change it
1835 if ( !strDesc.empty())
1836 m_aDescriptions[nIndex] = strDesc;
1837
1838 // if new icon change it
1839 if ( !strIcon.empty())
1840 m_aIcons[nIndex] = strIcon;
1841
1842 if ( entry )
1843 {
1844 delete m_aEntries[nIndex];
1845 m_aEntries[nIndex] = entry;
1846 }
1847 }
1848 else // add data we don't already have ...
1849 {
1850 // if new description add only if none
1851 if ( m_aDescriptions[nIndex].empty() )
1852 m_aDescriptions[nIndex] = strDesc;
1853
1854 // if new icon and no existing icon
1855 if ( m_aIcons[nIndex].empty() )
1856 m_aIcons[nIndex] = strIcon;
1857
1858 // add any new entries...
1859 if ( entry )
1860 {
1861 wxMimeTypeCommands *entryOld = m_aEntries[nIndex];
1862
1863 size_t count = entry->GetCount();
1864 for ( size_t i = 0; i < count; i++ )
1865 {
1866 const wxString& verb = entry->GetVerb(i);
1867 if ( !entryOld->HasVerb(verb) )
1868 {
1869 entryOld->AddOrReplaceVerb(verb, entry->GetCmd(i));
1870 }
1871 }
1872
1873 // as we don't store it anywhere, it won't be deleted later as
1874 // usual -- do it immediately instead
1875 delete entry;
1876 }
1877 }
1878 }
1879
1880 // always add the extensions to this mimetype
1881 wxString& exts = m_aExtensions[nIndex];
1882
1883 // add all extensions we don't have yet
1884 size_t count = strExtensions.GetCount();
1885 for ( size_t i = 0; i < count; i++ )
1886 {
1887 wxString ext = strExtensions[i] + wxT(' ');
1888
1889 if ( exts.Find(ext) == wxNOT_FOUND )
1890 {
1891 exts += ext;
1892 }
1893 }
1894
1895 // check data integrity
1896 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1897 m_aTypes.Count() == m_aExtensions.Count() &&
1898 m_aTypes.Count() == m_aIcons.Count() &&
1899 m_aTypes.Count() == m_aDescriptions.Count() );
1900
1901 return nIndex;
1902 }
1903
1904 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1905 {
1906 if (ext.empty() )
1907 return NULL;
1908
1909 InitIfNeeded();
1910
1911 size_t count = m_aExtensions.GetCount();
1912 for ( size_t n = 0; n < count; n++ )
1913 {
1914 wxStringTokenizer tk(m_aExtensions[n], wxT(' '));
1915
1916 while ( tk.HasMoreTokens() )
1917 {
1918 // consider extensions as not being case-sensitive
1919 if ( tk.GetNextToken().IsSameAs(ext, false /* no case */) )
1920 {
1921 // found
1922 wxFileType *fileType = new wxFileType;
1923 fileType->m_impl->Init(this, n);
1924
1925 return fileType;
1926 }
1927 }
1928 }
1929
1930 return NULL;
1931 }
1932
1933 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1934 {
1935 InitIfNeeded();
1936
1937 wxFileType * fileType = NULL;
1938 // mime types are not case-sensitive
1939 wxString mimetype(mimeType);
1940 mimetype.MakeLower();
1941
1942 // first look for an exact match
1943 int index = m_aTypes.Index(mimetype);
1944 if ( index != wxNOT_FOUND )
1945 {
1946 fileType = new wxFileType;
1947 fileType->m_impl->Init(this, index);
1948 }
1949
1950 // then try to find "text/*" as match for "text/plain" (for example)
1951 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1952 // the whole string - ok.
1953
1954 index = wxNOT_FOUND;
1955 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
1956
1957 size_t nCount = m_aTypes.Count();
1958 for ( size_t n = 0; n < nCount; n++ )
1959 {
1960 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1961 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") )
1962 {
1963 index = n;
1964 break;
1965 }
1966 }
1967
1968 if ( index != wxNOT_FOUND )
1969 {
1970 // don't throw away fileType that was already found
1971 if (!fileType)
1972 fileType = new wxFileType;
1973 fileType->m_impl->Init(this, index);
1974 }
1975
1976 return fileType;
1977 }
1978
1979 wxString wxMimeTypesManagerImpl::GetCommand(const wxString & verb, size_t nIndex) const
1980 {
1981 wxString command, testcmd, sV, sTmp;
1982 sV = verb + wxT("=");
1983
1984 // list of verb = command pairs for this mimetype
1985 wxMimeTypeCommands * sPairs = m_aEntries [nIndex];
1986
1987 size_t i;
1988 for ( i = 0; i < sPairs->GetCount (); i++ )
1989 {
1990 sTmp = sPairs->GetVerbCmd (i);
1991 if ( sTmp.Contains(sV) )
1992 command = sTmp.AfterFirst(wxT('='));
1993 }
1994
1995 return command;
1996 }
1997
1998 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1999 {
2000 InitIfNeeded();
2001
2002 wxString extensions;
2003 const wxArrayString& exts = filetype.GetExtensions();
2004 size_t nExts = exts.GetCount();
2005 for ( size_t nExt = 0; nExt < nExts; nExt++ )
2006 {
2007 if ( nExt > 0 )
2008 extensions += wxT(' ');
2009
2010 extensions += exts[nExt];
2011 }
2012
2013 AddMimeTypeInfo(filetype.GetMimeType(),
2014 extensions,
2015 filetype.GetDescription());
2016
2017 AddMailcapInfo(filetype.GetMimeType(),
2018 filetype.GetOpenCommand(),
2019 filetype.GetPrintCommand(),
2020 wxT(""),
2021 filetype.GetDescription());
2022 }
2023
2024 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
2025 const wxString& strExtensions,
2026 const wxString& strDesc)
2027 {
2028 // reading mailcap may find image/* , while
2029 // reading mime.types finds image/gif and no match is made
2030 // this means all the get functions don't work fix this
2031 wxString strIcon;
2032 wxString sTmp = strExtensions;
2033
2034 wxArrayString sExts;
2035 sTmp.Trim().Trim(false);
2036
2037 while (!sTmp.empty())
2038 {
2039 sExts.Add(sTmp.AfterLast(wxT(' ')));
2040 sTmp = sTmp.BeforeLast(wxT(' '));
2041 }
2042
2043 AddToMimeData(strMimeType, strIcon, NULL, sExts, strDesc, true);
2044 }
2045
2046 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
2047 const wxString& strOpenCmd,
2048 const wxString& strPrintCmd,
2049 const wxString& strTest,
2050 const wxString& strDesc)
2051 {
2052 InitIfNeeded();
2053
2054 wxMimeTypeCommands *entry = new wxMimeTypeCommands;
2055 entry->Add(wxT("open=") + strOpenCmd);
2056 entry->Add(wxT("print=") + strPrintCmd);
2057 entry->Add(wxT("test=") + strTest);
2058
2059 wxString strIcon;
2060 wxArrayString strExtensions;
2061
2062 AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
2063 }
2064
2065 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
2066 {
2067 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
2068 strFileName.c_str());
2069
2070 wxTextFile file(strFileName);
2071 #if defined(__WXGTK20__) && wxUSE_UNICODE
2072 if ( !file.Open(wxConvUTF8) )
2073 #else
2074 if ( !file.Open() )
2075 #endif
2076 return false;
2077
2078 // the information we extract
2079 wxString strMimeType, strDesc, strExtensions;
2080
2081 size_t nLineCount = file.GetLineCount();
2082 const wxChar *pc = NULL;
2083 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2084 {
2085 if ( pc == NULL )
2086 {
2087 // now we're at the start of the line
2088 pc = file[nLine].c_str();
2089 }
2090 else
2091 {
2092 // we didn't finish with the previous line yet
2093 nLine--;
2094 }
2095
2096 // skip whitespace
2097 while ( wxIsspace(*pc) )
2098 pc++;
2099
2100 // comment or blank line?
2101 if ( *pc == wxT('#') || !*pc )
2102 {
2103 // skip the whole line
2104 pc = NULL;
2105 continue;
2106 }
2107
2108 // detect file format
2109 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
2110 if ( pEqualSign == NULL )
2111 {
2112 // brief format
2113 // ------------
2114
2115 // first field is mime type
2116 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ )
2117 {
2118 strMimeType += *pc;
2119 }
2120
2121 // skip whitespace
2122 while ( wxIsspace(*pc) )
2123 pc++;
2124
2125 // take all the rest of the string
2126 strExtensions = pc;
2127
2128 // no description...
2129 strDesc.Empty();
2130 }
2131 else
2132 {
2133 // expanded format
2134 // ---------------
2135
2136 // the string on the left of '=' is the field name
2137 wxString strLHS(pc, pEqualSign - pc);
2138
2139 // eat whitespace
2140 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
2141 ;
2142
2143 const wxChar *pEnd;
2144 if ( *pc == wxT('"') )
2145 {
2146 // the string is quoted and ends at the matching quote
2147 pEnd = wxStrchr(++pc, wxT('"'));
2148 if ( pEnd == NULL )
2149 {
2150 wxLogWarning(wxT("Mime.types file %s, line %lu: unterminated quoted string."),
2151 strFileName.c_str(), nLine + 1L);
2152 }
2153 }
2154 else
2155 {
2156 // unquoted string ends at the first space or at the end of
2157 // line
2158 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
2159 ;
2160 }
2161
2162 // now we have the RHS (field value)
2163 wxString strRHS(pc, pEnd - pc);
2164
2165 // check what follows this entry
2166 if ( *pEnd == wxT('"') )
2167 {
2168 // skip this quote
2169 pEnd++;
2170 }
2171
2172 for ( pc = pEnd; wxIsspace(*pc); pc++ )
2173 ;
2174
2175 // if there is something left, it may be either a '\\' to continue
2176 // the line or the next field of the same entry
2177 bool entryEnded = *pc == wxT('\0');
2178 bool nextFieldOnSameLine = false;
2179 if ( !entryEnded )
2180 {
2181 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
2182 }
2183
2184 // now see what we got
2185 if ( strLHS == wxT("type") )
2186 {
2187 strMimeType = strRHS;
2188 }
2189 else if ( strLHS.StartsWith(wxT("desc")) )
2190 {
2191 strDesc = strRHS;
2192 }
2193 else if ( strLHS == wxT("exts") )
2194 {
2195 strExtensions = strRHS;
2196 }
2197 else if ( strLHS == wxT("icon") )
2198 {
2199 // this one is simply ignored: it usually refers to Netscape
2200 // built in icons which are useless for us anyhow
2201 }
2202 else if ( !strLHS.StartsWith(wxT("x-")) )
2203 {
2204 // we suppose that all fields starting with "X-" are
2205 // unregistered extensions according to the standard practice,
2206 // but it may be worth telling the user about other junk in
2207 // his mime.types file
2208 wxLogWarning(wxT("Unknown field in file %s, line %lu: '%s'."),
2209 strFileName.c_str(), nLine + 1L, strLHS.c_str());
2210 }
2211
2212 if ( !entryEnded )
2213 {
2214 if ( !nextFieldOnSameLine )
2215 pc = NULL;
2216 //else: don't reset it
2217
2218 // as we don't reset strMimeType, the next field in this entry
2219 // will be interpreted correctly.
2220
2221 continue;
2222 }
2223 }
2224
2225 // depending on the format (Mosaic or Netscape) either space or comma
2226 // is used to separate the extensions
2227 strExtensions.Replace(wxT(","), wxT(" "));
2228
2229 // also deal with the leading dot
2230 if ( !strExtensions.empty() && strExtensions[0u] == wxT('.') )
2231 {
2232 strExtensions.erase(0, 1);
2233 }
2234
2235 wxLogTrace(TRACE_MIME, wxT("mime.types: '%s' => '%s' (%s)"),
2236 strExtensions.c_str(),
2237 strMimeType.c_str(),
2238 strDesc.c_str());
2239
2240 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
2241
2242 // finished with this line
2243 pc = NULL;
2244 }
2245
2246 return true;
2247 }
2248
2249 // ----------------------------------------------------------------------------
2250 // UNIX mailcap files parsing
2251 // ----------------------------------------------------------------------------
2252
2253 // the data for a single MIME type
2254 struct MailcapLineData
2255 {
2256 // field values
2257 wxString type,
2258 cmdOpen,
2259 test,
2260 icon,
2261 desc;
2262
2263 wxArrayString verbs,
2264 commands;
2265
2266 // flags
2267 bool testfailed,
2268 needsterminal,
2269 copiousoutput;
2270
2271 MailcapLineData() { testfailed = needsterminal = copiousoutput = false; }
2272 };
2273
2274 // process a non-standard (i.e. not the first or second one) mailcap field
2275 bool
2276 wxMimeTypesManagerImpl::ProcessOtherMailcapField(MailcapLineData& data,
2277 const wxString& curField)
2278 {
2279 if ( curField.empty() )
2280 {
2281 // we don't care
2282 return true;
2283 }
2284
2285 // is this something of the form foo=bar?
2286 const wxChar *pEq = wxStrchr(curField, wxT('='));
2287 if ( pEq != NULL )
2288 {
2289 // split "LHS = RHS" in 2
2290 wxString lhs = curField.BeforeFirst(wxT('=')),
2291 rhs = curField.AfterFirst(wxT('='));
2292
2293 lhs.Trim(true); // from right
2294 rhs.Trim(false); // from left
2295
2296 // it might be quoted
2297 if ( !rhs.empty() && rhs[0u] == wxT('"') && rhs.Last() == wxT('"') )
2298 {
2299 rhs = rhs.Mid(1, rhs.length() - 2);
2300 }
2301
2302 // is it a command verb or something else?
2303 if ( lhs == wxT("test") )
2304 {
2305 if ( wxSystem(rhs) == 0 )
2306 {
2307 // ok, test passed
2308 wxLogTrace(TRACE_MIME_TEST,
2309 wxT("Test '%s' for mime type '%s' succeeded."),
2310 rhs.c_str(), data.type.c_str());
2311 }
2312 else
2313 {
2314 wxLogTrace(TRACE_MIME_TEST,
2315 wxT("Test '%s' for mime type '%s' failed, skipping."),
2316 rhs.c_str(), data.type.c_str());
2317
2318 data.testfailed = true;
2319 }
2320 }
2321 else if ( lhs == wxT("desc") )
2322 {
2323 data.desc = rhs;
2324 }
2325 else if ( lhs == wxT("x11-bitmap") )
2326 {
2327 data.icon = rhs;
2328 }
2329 else if ( lhs == wxT("notes") )
2330 {
2331 // ignore
2332 }
2333 else // not a (recognized) special case, must be a verb (e.g. "print")
2334 {
2335 data.verbs.Add(lhs);
2336 data.commands.Add(rhs);
2337 }
2338 }
2339 else // '=' not found
2340 {
2341 // so it must be a simple flag
2342 if ( curField == wxT("needsterminal") )
2343 {
2344 data.needsterminal = true;
2345 }
2346 else if ( curField == wxT("copiousoutput"))
2347 {
2348 // copiousoutput impies that the viewer is a console program
2349 data.needsterminal =
2350 data.copiousoutput = true;
2351 }
2352 else if ( !IsKnownUnimportantField(curField) )
2353 {
2354 return false;
2355 }
2356 }
2357
2358 return true;
2359 }
2360
2361 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
2362 bool fallback)
2363 {
2364 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
2365 strFileName.c_str());
2366
2367 wxTextFile file(strFileName);
2368 #if defined(__WXGTK20__) && wxUSE_UNICODE
2369 if ( !file.Open(wxConvUTF8) )
2370 #else
2371 if ( !file.Open() )
2372 #endif
2373 return false;
2374
2375 // indices of MIME types (in m_aTypes) we already found in this file
2376 //
2377 // (see the comments near the end of function for the reason we need this)
2378 wxArrayInt aIndicesSeenHere;
2379
2380 // accumulator for the current field
2381 wxString curField;
2382 curField.reserve(1024);
2383
2384 size_t nLineCount = file.GetLineCount();
2385 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2386 {
2387 // now we're at the start of the line
2388 const wxChar *pc = file[nLine].c_str();
2389
2390 // skip whitespace
2391 while ( wxIsspace(*pc) )
2392 pc++;
2393
2394 // comment or empty string?
2395 if ( *pc == wxT('#') || *pc == wxT('\0') )
2396 continue;
2397
2398 // no, do parse
2399 // ------------
2400
2401 // what field are we currently in? The first 2 are fixed and there may
2402 // be an arbitrary number of other fields parsed by
2403 // ProcessOtherMailcapField()
2404 //
2405 // the first field is the MIME type
2406 enum
2407 {
2408 Field_Type,
2409 Field_OpenCmd,
2410 Field_Other
2411 }
2412 currentToken = Field_Type;
2413
2414 // the flags and field values on the current line
2415 MailcapLineData data;
2416
2417 bool cont = true;
2418 while ( cont )
2419 {
2420 switch ( *pc )
2421 {
2422 case wxT('\\'):
2423 // interpret the next character literally (notice that
2424 // backslash can be used for line continuation)
2425 if ( *++pc == wxT('\0') )
2426 {
2427 // fetch the next line if there is one
2428 if ( nLine == nLineCount - 1 )
2429 {
2430 // something is wrong, bail out
2431 cont = false;
2432
2433 wxLogDebug(wxT("Mailcap file %s, line %lu: '\\' on the end of the last line ignored."),
2434 strFileName.c_str(),
2435 nLine + 1L);
2436 }
2437 else
2438 {
2439 // pass to the beginning of the next line
2440 pc = file[++nLine].c_str();
2441
2442 // skip pc++ at the end of the loop
2443 continue;
2444 }
2445 }
2446 else
2447 {
2448 // just a normal character
2449 curField += *pc;
2450 }
2451 break;
2452
2453 case wxT('\0'):
2454 cont = false; // end of line reached, exit the loop
2455
2456 // fall through to still process this field
2457
2458 case wxT(';'):
2459 // trim whitespaces from both sides
2460 curField.Trim(true).Trim(false);
2461
2462 switch ( currentToken )
2463 {
2464 case Field_Type:
2465 data.type = curField.Lower();
2466 if ( data.type.empty() )
2467 {
2468 // I don't think that this is a valid mailcap
2469 // entry, but try to interpret it somehow
2470 data.type = wxT('*');
2471 }
2472
2473 if ( data.type.Find(wxT('/')) == wxNOT_FOUND )
2474 {
2475 // we interpret "type" as "type/*"
2476 data.type += wxT("/*");
2477 }
2478
2479 currentToken = Field_OpenCmd;
2480 break;
2481
2482 case Field_OpenCmd:
2483 data.cmdOpen = curField;
2484
2485 currentToken = Field_Other;
2486 break;
2487
2488 case Field_Other:
2489 if ( !ProcessOtherMailcapField(data, curField) )
2490 {
2491 // don't flood the user with error messages if
2492 // we don't understand something in his
2493 // mailcap, but give them in debug mode because
2494 // this might be useful for the programmer
2495 wxLogDebug
2496 (
2497 wxT("Mailcap file %s, line %lu: unknown field '%s' for the MIME type '%s' ignored."),
2498 strFileName.c_str(),
2499 nLine + 1L,
2500 curField.c_str(),
2501 data.type.c_str()
2502 );
2503 }
2504 else if ( data.testfailed )
2505 {
2506 // skip this entry entirely
2507 cont = false;
2508 }
2509
2510 // it already has this value
2511 //currentToken = Field_Other;
2512 break;
2513
2514 default:
2515 wxFAIL_MSG(wxT("unknown field type in mailcap"));
2516 }
2517
2518 // next token starts immediately after ';'
2519 curField.Empty();
2520 break;
2521
2522 default:
2523 curField += *pc;
2524 }
2525
2526 // continue in the same line
2527 pc++;
2528 }
2529
2530 // we read the entire entry, check what have we got
2531 // ------------------------------------------------
2532
2533 // check that we really read something reasonable
2534 if ( currentToken < Field_Other )
2535 {
2536 wxLogWarning(wxT("Mailcap file %s, line %lu: incomplete entry ignored."),
2537 strFileName.c_str(), nLine + 1L);
2538
2539 continue;
2540 }
2541
2542 // if the test command failed, it's as if the entry were not there at all
2543 if ( data.testfailed )
2544 {
2545 continue;
2546 }
2547
2548 // support for flags:
2549 // 1. create an xterm for 'needsterminal'
2550 // 2. append "| $PAGER" for 'copiousoutput'
2551 //
2552 // Note that the RFC says that having both needsterminal and
2553 // copiousoutput is probably a mistake, so it seems that running
2554 // programs with copiousoutput inside an xterm as it is done now
2555 // is a bad idea (FIXME)
2556 if ( data.copiousoutput )
2557 {
2558 const wxChar *p = wxGetenv(wxT("PAGER"));
2559 data.cmdOpen << wxT(" | ") << (p ? p : wxT("more"));
2560 }
2561
2562 if ( data.needsterminal )
2563 {
2564 data.cmdOpen = wxString::Format(wxT("xterm -e sh -c '%s'"),
2565 data.cmdOpen.c_str());
2566 }
2567
2568 if ( !data.cmdOpen.empty() )
2569 {
2570 data.verbs.Insert(wxT("open"), 0);
2571 data.commands.Insert(data.cmdOpen, 0);
2572 }
2573
2574 // we have to decide whether the new entry should replace any entries
2575 // for the same MIME type we had previously found or not
2576 bool overwrite;
2577
2578 // the fall back entries have the lowest priority, by definition
2579 if ( fallback )
2580 {
2581 overwrite = false;
2582 }
2583 else
2584 {
2585 // have we seen this one before?
2586 int nIndex = m_aTypes.Index(data.type);
2587
2588 // and if we have, was it in this file? if not, we should
2589 // overwrite the previously seen one
2590 overwrite = nIndex == wxNOT_FOUND ||
2591 aIndicesSeenHere.Index(nIndex) == wxNOT_FOUND;
2592 }
2593
2594 wxLogTrace(TRACE_MIME, wxT("mailcap %s: %s [%s]"),
2595 data.type.c_str(), data.cmdOpen.c_str(),
2596 overwrite ? wxT("replace") : wxT("add"));
2597
2598 int n = AddToMimeData
2599 (
2600 data.type,
2601 data.icon,
2602 new wxMimeTypeCommands(data.verbs, data.commands),
2603 wxArrayString() /* extensions */,
2604 data.desc,
2605 overwrite
2606 );
2607
2608 if ( overwrite )
2609 {
2610 aIndicesSeenHere.Add(n);
2611 }
2612 }
2613
2614 return true;
2615 }
2616
2617 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
2618 {
2619 InitIfNeeded();
2620
2621 mimetypes.Empty();
2622
2623 wxString type;
2624 size_t count = m_aTypes.GetCount();
2625 for ( size_t n = 0; n < count; n++ )
2626 {
2627 // don't return template types from here (i.e. anything containg '*')
2628 type = m_aTypes[n];
2629 if ( type.Find(wxT('*')) == wxNOT_FOUND )
2630 {
2631 mimetypes.Add(type);
2632 }
2633 }
2634
2635 return mimetypes.GetCount();
2636 }
2637
2638 // ----------------------------------------------------------------------------
2639 // writing to MIME type files
2640 // ----------------------------------------------------------------------------
2641
2642 bool wxMimeTypesManagerImpl::Unassociate(wxFileType *ft)
2643 {
2644 wxArrayString sMimeTypes;
2645 ft->GetMimeTypes(sMimeTypes);
2646
2647 wxString sMime;
2648 size_t i;
2649 for (i = 0; i < sMimeTypes.GetCount(); i ++)
2650 {
2651 sMime = sMimeTypes.Item(i);
2652 int nIndex = m_aTypes.Index(sMime);
2653 if ( nIndex == wxNOT_FOUND)
2654 {
2655 // error if we get here ??
2656 return false;
2657 }
2658 else
2659 {
2660 WriteMimeInfo(nIndex, true);
2661 m_aTypes.RemoveAt(nIndex);
2662 m_aEntries.RemoveAt(nIndex);
2663 m_aExtensions.RemoveAt(nIndex);
2664 m_aDescriptions.RemoveAt(nIndex);
2665 m_aIcons.RemoveAt(nIndex);
2666 }
2667 }
2668 // check data integrity
2669 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
2670 m_aTypes.Count() == m_aExtensions.Count() &&
2671 m_aTypes.Count() == m_aIcons.Count() &&
2672 m_aTypes.Count() == m_aDescriptions.Count() );
2673
2674 return true;
2675 }
2676
2677 // ----------------------------------------------------------------------------
2678 // private functions
2679 // ----------------------------------------------------------------------------
2680
2681 static bool IsKnownUnimportantField(const wxString& fieldAll)
2682 {
2683 static const wxChar *knownFields[] =
2684 {
2685 wxT("x-mozilla-flags"),
2686 wxT("nametemplate"),
2687 wxT("textualnewlines"),
2688 };
2689
2690 wxString field = fieldAll.BeforeFirst(wxT('='));
2691 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
2692 {
2693 if ( field.CmpNoCase(knownFields[n]) == 0 )
2694 return true;
2695 }
2696
2697 return false;
2698 }
2699
2700 #endif
2701 // wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE