]> git.saurik.com Git - wxWidgets.git/blob - src/unix/mimetype.cpp
applied wxNativeFontInfo patch from Derry Bryson (with minor changes)
[wxWidgets.git] / src / unix / mimetype.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: 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 license (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "mimetype.h"
22 #endif
23
24 // for compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32 #include "wx/defs.h"
33 #endif
34
35 #if wxUSE_FILE && wxUSE_TEXTFILE
36
37 #ifndef WX_PRECOMP
38 #include "wx/string.h"
39 #if wxUSE_GUI
40 #include "wx/icon.h"
41 #endif
42 #endif //WX_PRECOMP
43
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
57 #include "wx/unix/mimetype.h"
58
59 // other standard headers
60 #include <ctype.h>
61
62 // in case we're compiling in non-GUI mode
63 class WXDLLEXPORT wxIcon;
64
65 // ----------------------------------------------------------------------------
66 // constants
67 // ----------------------------------------------------------------------------
68
69 // MIME code tracing mask
70 #define TRACE_MIME _T("mime")
71
72 // ----------------------------------------------------------------------------
73 // private functions
74 // ----------------------------------------------------------------------------
75
76 // there are some fields which we don't understand but for which we don't give
77 // warnings as we know that they're not important - this function is used to
78 // test for them
79 static bool IsKnownUnimportantField(const wxString& field);
80
81 // ----------------------------------------------------------------------------
82 // private classes
83 // ----------------------------------------------------------------------------
84
85
86 // this class uses both mailcap and mime.types to gather information about file
87 // types.
88 //
89 // The information about mailcap file was extracted from metamail(1) sources and
90 // documentation.
91 //
92 // Format of mailcap file: spaces are ignored, each line is either a comment
93 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
94 // A backslash can be used to quote semicolons and newlines (and, in fact,
95 // anything else including itself).
96 //
97 // The first field is always the MIME type in the form of type/subtype (see RFC
98 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
99 // "type" which means the same as "type/*", although I'm not sure whether this
100 // is standard.
101 //
102 // The second field is always the command to run. It is subject to
103 // parameter/filename expansion described below.
104 //
105 // All the following fields are optional and may not be present at all. If
106 // they're present they may appear in any order, although each of them should
107 // appear only once. The optional fields are the following:
108 // * notes=xxx is an uninterpreted string which is silently ignored
109 // * test=xxx is the command to be used to determine whether this mailcap line
110 // applies to our data or not. The RHS of this field goes through the
111 // parameter/filename expansion (as the 2nd field) and the resulting string
112 // is executed. The line applies only if the command succeeds, i.e. returns 0
113 // exit code.
114 // * print=xxx is the command to be used to print (and not view) the data of
115 // this type (parameter/filename expansion is done here too)
116 // * edit=xxx is the command to open/edit the data of this type
117 // * needsterminal means that a new console must be created for the viewer
118 // * copiousoutput means that the viewer doesn't interact with the user but
119 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
120 // good example), thus it might be a good idea to use some kind of paging
121 // mechanism.
122 // * textualnewlines means not to perform CR/LF translation (not honored)
123 // * compose and composetyped fields are used to determine the program to be
124 // called to create a new message pert in the specified format (unused).
125 //
126 // Parameter/filename xpansion:
127 // * %s is replaced with the (full) file name
128 // * %t is replaced with MIME type/subtype of the entry
129 // * for multipart type only %n is replaced with the nnumber of parts and %F is
130 // replaced by an array of (content-type, temporary file name) pairs for all
131 // message parts (TODO)
132 // * %{parameter} is replaced with the value of parameter taken from
133 // Content-type header line of the message.
134 //
135 // FIXME any docs with real descriptions of these files??
136 //
137 // There are 2 possible formats for mime.types file, one entry per line (used
138 // for global mime.types) and "expanded" format where an entry takes multiple
139 // lines (used for users mime.types).
140 //
141 // For both formats spaces are ignored and lines starting with a '#' are
142 // comments. Each record has one of two following forms:
143 // a) for "brief" format:
144 // <mime type> <space separated list of extensions>
145 // b) for "expanded" format:
146 // type=<mime type> \ desc="<description>" \ exts="ext"
147 //
148 // We try to autodetect the format of mime.types: if a non-comment line starts
149 // with "type=" we assume the second format, otherwise the first one.
150
151 // there may be more than one entry for one and the same mime type, to
152 // choose the right one we have to run the command specified in the test
153 // field on our data.
154 class MailCapEntry
155 {
156 public:
157 // ctor
158 MailCapEntry(const wxString& openCmd,
159 const wxString& printCmd,
160 const wxString& testCmd)
161 : m_openCmd(openCmd), m_printCmd(printCmd), m_testCmd(testCmd)
162 {
163 m_next = NULL;
164 }
165
166 ~MailCapEntry()
167 {
168 if (m_next) delete m_next;
169 }
170
171 // accessors
172 const wxString& GetOpenCmd() const { return m_openCmd; }
173 const wxString& GetPrintCmd() const { return m_printCmd; }
174 const wxString& GetTestCmd() const { return m_testCmd; }
175
176 MailCapEntry *GetNext() const { return m_next; }
177
178 // operations
179 // prepend this element to the list
180 void Prepend(MailCapEntry *next) { m_next = next; }
181 // insert into the list at given position
182 void Insert(MailCapEntry *next, size_t pos)
183 {
184 // FIXME slooow...
185 MailCapEntry *cur;
186 size_t n = 0;
187 for ( cur = next; cur != NULL; cur = cur->m_next, n++ ) {
188 if ( n == pos )
189 break;
190 }
191
192 wxASSERT_MSG( n == pos, wxT("invalid position in MailCapEntry::Insert") );
193
194 m_next = cur->m_next;
195 cur->m_next = this;
196 }
197 // append this element to the list
198 void Append(MailCapEntry *next)
199 {
200 wxCHECK_RET( next != NULL, wxT("Append()ing to what?") );
201
202 // FIXME slooow...
203 MailCapEntry *cur;
204 for ( cur = next; cur->m_next != NULL; cur = cur->m_next )
205 ;
206
207 cur->m_next = this;
208
209 wxASSERT_MSG( !m_next, wxT("Append()ing element already in the list?") );
210 }
211
212 private:
213 wxString m_openCmd, // command to use to open/view the file
214 m_printCmd, // print
215 m_testCmd; // only apply this entry if test yields
216 // true (i.e. the command returns 0)
217
218 MailCapEntry *m_next; // in the linked list
219 };
220
221
222 // the base class which may be used to find an icon for the MIME type
223 class wxMimeTypeIconHandler
224 {
225 public:
226 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon) = 0;
227
228 // this function fills manager with MIME types information gathered
229 // (as side effect) when searching for icons. This may be particularly
230 // useful if mime.types is incomplete (e.g. RedHat distributions).
231 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager) = 0;
232 };
233
234
235 // the icon handler which uses GNOME MIME database
236 class wxGNOMEIconHandler : public wxMimeTypeIconHandler
237 {
238 public:
239 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
240 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
241
242 private:
243 void Init();
244 void LoadIconsFromKeyFile(const wxString& filename);
245 void LoadKeyFilesFromDir(const wxString& dirbase);
246
247 void LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager);
248 void LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager);
249
250 static bool m_inited;
251
252 static wxSortedArrayString ms_mimetypes;
253 static wxArrayString ms_icons;
254 };
255
256 // the icon handler which uses KDE MIME database
257 class wxKDEIconHandler : public wxMimeTypeIconHandler
258 {
259 public:
260 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
261 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
262
263 private:
264 void LoadLinksForMimeSubtype(const wxString& dirbase,
265 const wxString& subdir,
266 const wxString& filename,
267 const wxArrayString& icondirs);
268 void LoadLinksForMimeType(const wxString& dirbase,
269 const wxString& subdir,
270 const wxArrayString& icondirs);
271 void LoadLinkFilesFromDir(const wxString& dirbase,
272 const wxArrayString& icondirs);
273 void Init();
274
275 static bool m_inited;
276
277 static wxSortedArrayString ms_mimetypes;
278 static wxArrayString ms_icons;
279
280 static wxArrayString ms_infoTypes;
281 static wxArrayString ms_infoDescriptions;
282 static wxArrayString ms_infoExtensions;
283 };
284
285
286
287 // ----------------------------------------------------------------------------
288 // various statics
289 // ----------------------------------------------------------------------------
290
291 static wxGNOMEIconHandler gs_iconHandlerGNOME;
292 static wxKDEIconHandler gs_iconHandlerKDE;
293
294 bool wxGNOMEIconHandler::m_inited = FALSE;
295 wxSortedArrayString wxGNOMEIconHandler::ms_mimetypes;
296 wxArrayString wxGNOMEIconHandler::ms_icons;
297
298 bool wxKDEIconHandler::m_inited = FALSE;
299 wxSortedArrayString wxKDEIconHandler::ms_mimetypes;
300 wxArrayString wxKDEIconHandler::ms_icons;
301
302 wxArrayString wxKDEIconHandler::ms_infoTypes;
303 wxArrayString wxKDEIconHandler::ms_infoDescriptions;
304 wxArrayString wxKDEIconHandler::ms_infoExtensions;
305
306
307 ArrayIconHandlers wxMimeTypesManagerImpl::ms_iconHandlers;
308
309 // ----------------------------------------------------------------------------
310 // wxGNOMEIconHandler
311 // ----------------------------------------------------------------------------
312
313 // GNOME stores the info we're interested in in several locations:
314 // 1. xxx.keys files under /usr/share/mime-info
315 // 2. xxx.keys files under ~/.gnome/mime-info
316 //
317 // The format of xxx.keys file is the following:
318 //
319 // mimetype/subtype:
320 // field=value
321 //
322 // with blank lines separating the entries and indented lines starting with
323 // TABs. We're interested in the field icon-filename whose value is the path
324 // containing the icon.
325
326 void wxGNOMEIconHandler::LoadIconsFromKeyFile(const wxString& filename)
327 {
328 wxTextFile textfile(filename);
329 if ( !textfile.Open() )
330 return;
331
332 // values for the entry being parsed
333 wxString curMimeType, curIconFile;
334
335 const wxChar *pc;
336 size_t nLineCount = textfile.GetLineCount();
337 for ( size_t nLine = 0; ; nLine++ )
338 {
339 if ( nLine < nLineCount )
340 {
341 pc = textfile[nLine].c_str();
342 if ( *pc == _T('#') )
343 {
344 // skip comments
345 continue;
346 }
347 }
348 else
349 {
350 // so that we will fall into the "if" below
351 pc = NULL;
352 }
353
354 if ( !pc || !*pc )
355 {
356 // end of the entry
357 if ( !!curMimeType && !!curIconFile )
358 {
359 // do we already know this mimetype?
360 int i = ms_mimetypes.Index(curMimeType);
361 if ( i == wxNOT_FOUND )
362 {
363 // add a new entry
364 size_t n = ms_mimetypes.Add(curMimeType);
365 ms_icons.Insert(curIconFile, n);
366 }
367 else
368 {
369 // replace the existing one (this means that the directories
370 // should be searched in order of increased priority!)
371 ms_icons[(size_t)i] = curIconFile;
372 }
373 }
374
375 if ( !pc )
376 {
377 // the end - this can only happen if nLine == nLineCount
378 break;
379 }
380
381 curIconFile.Empty();
382
383 continue;
384 }
385
386 // what do we have here?
387 if ( *pc == _T('\t') )
388 {
389 // this is a field=value ling
390 pc++; // skip leading TAB
391
392 static const int lenField = 13; // strlen("icon-filename")
393 if ( wxStrncmp(pc, _T("icon-filename"), lenField) == 0 )
394 {
395 // skip '=' which follows and take everything left until the end
396 // of line
397 curIconFile = pc + lenField + 1;
398 }
399 //else: some other field, we don't care
400 }
401 else
402 {
403 // this is the start of the new section
404 curMimeType.Empty();
405
406 while ( *pc != _T(':') && *pc != _T('\0') )
407 {
408 curMimeType += *pc++;
409 }
410 }
411 }
412 }
413
414 void wxGNOMEIconHandler::LoadKeyFilesFromDir(const wxString& dirbase)
415 {
416 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
417 _T("base directory shouldn't end with a slash") );
418
419 wxString dirname = dirbase;
420 dirname << _T("/mime-info");
421
422 if ( !wxDir::Exists(dirname) )
423 return;
424
425 wxDir dir(dirname);
426 if ( !dir.IsOpened() )
427 return;
428
429 // we will concatenate it with filename to get the full path below
430 dirname += _T('/');
431
432 wxString filename;
433 bool cont = dir.GetFirst(&filename, _T("*.keys"), wxDIR_FILES);
434 while ( cont )
435 {
436 LoadIconsFromKeyFile(dirname + filename);
437
438 cont = dir.GetNext(&filename);
439 }
440 }
441
442
443 void wxGNOMEIconHandler::LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager)
444 {
445 wxTextFile textfile(filename);
446 if ( !textfile.Open() )
447 return;
448
449 // values for the entry being parsed
450 wxString curMimeType, curExtList;
451
452 const wxChar *pc;
453 size_t nLineCount = textfile.GetLineCount();
454 for ( size_t nLine = 0; ; nLine++ )
455 {
456 if ( nLine < nLineCount )
457 {
458 pc = textfile[nLine].c_str();
459 if ( *pc == _T('#') )
460 {
461 // skip comments
462 continue;
463 }
464 }
465 else
466 {
467 // so that we will fall into the "if" below
468 pc = NULL;
469 }
470
471 if ( !pc || !*pc )
472 {
473 // end of the entry
474 if ( !!curMimeType && !!curExtList )
475 {
476 manager -> AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
477 }
478
479 if ( !pc )
480 {
481 // the end - this can only happen if nLine == nLineCount
482 break;
483 }
484
485 curExtList.Empty();
486
487 continue;
488 }
489
490 // what do we have here?
491 if ( *pc == _T('\t') )
492 {
493 // this is a field=value ling
494 pc++; // skip leading TAB
495
496 static const int lenField = 4; // strlen("ext:")
497 if ( wxStrncmp(pc, _T("ext:"), lenField) == 0 )
498 {
499 // skip ' ' which follows and take everything left until the end
500 // of line
501 curExtList = pc + lenField + 1;
502 }
503 //else: some other field, we don't care
504 }
505 else
506 {
507 // this is the start of the new section
508 curMimeType.Empty();
509
510 while ( *pc != _T(':') && *pc != _T('\0') )
511 {
512 curMimeType += *pc++;
513 }
514 }
515 }
516 }
517
518
519 void wxGNOMEIconHandler::LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager)
520 {
521 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
522 _T("base directory shouldn't end with a slash") );
523
524 wxString dirname = dirbase;
525 dirname << _T("/mime-info");
526
527 if ( !wxDir::Exists(dirname) )
528 return;
529
530 wxDir dir(dirname);
531 if ( !dir.IsOpened() )
532 return;
533
534 // we will concatenate it with filename to get the full path below
535 dirname += _T('/');
536
537 wxString filename;
538 bool cont = dir.GetFirst(&filename, _T("*.mime"), wxDIR_FILES);
539 while ( cont )
540 {
541 LoadMimeTypesFromMimeFile(dirname + filename, manager);
542
543 cont = dir.GetNext(&filename);
544 }
545 }
546
547
548 void wxGNOMEIconHandler::Init()
549 {
550 wxArrayString dirs;
551 dirs.Add(_T("/usr/share"));
552 dirs.Add(_T("/usr/local/share"));
553
554 wxString gnomedir;
555 wxGetHomeDir( &gnomedir );
556 gnomedir += _T("/.gnome");
557 dirs.Add( gnomedir );
558
559 size_t nDirs = dirs.GetCount();
560 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
561 {
562 LoadKeyFilesFromDir(dirs[nDir]);
563 }
564
565 m_inited = TRUE;
566 }
567
568
569 void wxGNOMEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
570 {
571 if ( !m_inited )
572 {
573 Init();
574 }
575
576 wxArrayString dirs;
577 dirs.Add(_T("/usr/share"));
578 dirs.Add(_T("/usr/local/share"));
579
580 wxString gnomedir;
581 wxGetHomeDir( &gnomedir );
582 gnomedir += _T("/.gnome");
583 dirs.Add( gnomedir );
584
585 size_t nDirs = dirs.GetCount();
586 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
587 {
588 LoadMimeFilesFromDir(dirs[nDir], manager);
589 }
590 }
591
592 #if wxUSE_GUI
593 #define WXUNUSED_UNLESS_GUI(p) p
594 #else
595 #define WXUNUSED_UNLESS_GUI(p)
596 #endif
597
598 bool wxGNOMEIconHandler::GetIcon(const wxString& mimetype,
599 wxIcon * WXUNUSED_UNLESS_GUI(icon))
600 {
601 if ( !m_inited )
602 {
603 Init();
604 }
605
606 int index = ms_mimetypes.Index(mimetype);
607 if ( index == wxNOT_FOUND )
608 return FALSE;
609
610 wxString iconname = ms_icons[(size_t)index];
611
612 #if wxUSE_GUI
613 wxLogNull nolog;
614 wxIcon icn;
615 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
616 icn = wxIcon(iconname);
617 else
618 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
619 if ( !icn.Ok() )
620 return FALSE;
621
622 if ( icon )
623 *icon = icn;
624 #else
625 // helpful for testing in console mode
626 wxLogDebug(_T("Found GNOME icon for '%s': '%s'\n"),
627 mimetype.c_str(), iconname.c_str());
628 #endif
629
630 return TRUE;
631 }
632
633 // ----------------------------------------------------------------------------
634 // wxKDEIconHandler
635 // ----------------------------------------------------------------------------
636
637 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
638 // may be found in either of the following locations
639 //
640 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
641 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
642 //
643 // The format of a .kdelnk file is almost the same as the one used by
644 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
645 // value for the entry "Type"
646
647 void wxKDEIconHandler::LoadLinksForMimeSubtype(const wxString& dirbase,
648 const wxString& subdir,
649 const wxString& filename,
650 const wxArrayString& icondirs)
651 {
652 wxFFile file(dirbase + filename);
653 if ( !file.IsOpened() )
654 return;
655
656 // construct mimetype from the directory name and the basename of the
657 // file (it always has .kdelnk extension)
658 wxString mimetype;
659 mimetype << subdir << _T('/') << filename.BeforeLast(_T('.'));
660
661 // these files are small, slurp the entire file at once
662 wxString text;
663 if ( !file.ReadAll(&text) )
664 return;
665
666 int pos;
667 const wxChar *pc;
668
669 // before trying to find an icon, grab mimetype information
670 // (because BFU's machine would hardly have well-edited mime.types but (s)he might
671 // have edited it in control panel...)
672
673 wxString mime_extension, mime_desc;
674
675 pos = wxNOT_FOUND;
676 if (wxGetLocale() != NULL)
677 mime_desc = _T("Comment[") + wxGetLocale()->GetName() + _T("]=");
678 if (pos == wxNOT_FOUND) mime_desc = _T("Comment=");
679 pos = text.Find(mime_desc);
680 if (pos == wxNOT_FOUND) mime_desc = wxEmptyString;
681 else
682 {
683 pc = text.c_str() + pos + mime_desc.Length();
684 mime_desc = wxEmptyString;
685 while ( *pc && *pc != _T('\n') ) mime_desc += *pc++;
686 }
687
688 pos = text.Find(_T("Patterns="));
689 if (pos != wxNOT_FOUND)
690 {
691 wxString exts;
692 pc = text.c_str() + pos + 9;
693 while ( *pc && *pc != _T('\n') ) exts += *pc++;
694 wxStringTokenizer tokenizer(exts, _T(";"));
695 wxString e;
696
697 while (tokenizer.HasMoreTokens())
698 {
699 e = tokenizer.GetNextToken();
700 if (e.Left(2) != _T("*.")) continue; // don't support too difficult patterns
701 mime_extension << e.Mid(2);
702 mime_extension << _T(' ');
703 }
704 mime_extension.RemoveLast();
705 }
706
707 ms_infoTypes.Add(mimetype);
708 ms_infoDescriptions.Add(mime_desc);
709 ms_infoExtensions.Add(mime_extension);
710
711 // ok, now we can take care of icon:
712
713 pos = text.Find(_T("Icon="));
714 if ( pos == wxNOT_FOUND )
715 {
716 // no icon info
717 return;
718 }
719
720 wxString icon;
721
722 pc = text.c_str() + pos + 5; // 5 == strlen("Icon=")
723 while ( *pc && *pc != _T('\n') )
724 {
725 icon += *pc++;
726 }
727
728 if ( !!icon )
729 {
730 // we must check if the file exists because it may be stored
731 // in many locations, at least ~/.kde and $KDEDIR
732 size_t nDir, nDirs = icondirs.GetCount();
733 for ( nDir = 0; nDir < nDirs; nDir++ )
734 if (wxFileExists(icondirs[nDir] + icon))
735 {
736 icon.Prepend(icondirs[nDir]);
737 break;
738 }
739 if (nDir == nDirs) return; //does not exist
740
741 // do we already have this MIME type?
742 int i = ms_mimetypes.Index(mimetype);
743 if ( i == wxNOT_FOUND )
744 {
745 // add it
746 size_t n = ms_mimetypes.Add(mimetype);
747 ms_icons.Insert(icon, n);
748 }
749 else
750 {
751 // replace the old value
752 ms_icons[(size_t)i] = icon;
753 }
754 }
755 }
756
757 void wxKDEIconHandler::LoadLinksForMimeType(const wxString& dirbase,
758 const wxString& subdir,
759 const wxArrayString& icondirs)
760 {
761 wxString dirname = dirbase;
762 dirname += subdir;
763 wxDir dir(dirname);
764 if ( !dir.IsOpened() )
765 return;
766
767 dirname += _T('/');
768
769 wxString filename;
770 bool cont = dir.GetFirst(&filename, _T("*.kdelnk"), wxDIR_FILES);
771 while ( cont )
772 {
773 LoadLinksForMimeSubtype(dirname, subdir, filename, icondirs);
774
775 cont = dir.GetNext(&filename);
776 }
777 }
778
779 void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString& dirbase,
780 const wxArrayString& icondirs)
781 {
782 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
783 _T("base directory shouldn't end with a slash") );
784
785 wxString dirname = dirbase;
786 dirname << _T("/mimelnk");
787
788 if ( !wxDir::Exists(dirname) )
789 return;
790
791 wxDir dir(dirname);
792 if ( !dir.IsOpened() )
793 return;
794
795 // we will concatenate it with dir name to get the full path below
796 dirname += _T('/');
797
798 wxString subdir;
799 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
800 while ( cont )
801 {
802 LoadLinksForMimeType(dirname, subdir, icondirs);
803
804 cont = dir.GetNext(&subdir);
805 }
806 }
807
808 void wxKDEIconHandler::Init()
809 {
810 wxArrayString dirs;
811 wxArrayString icondirs;
812
813 // settings in ~/.kde have maximal priority
814 dirs.Add(wxGetHomeDir() + _T("/.kde/share"));
815 icondirs.Add(wxGetHomeDir() + _T("/.kde/share/icons/"));
816
817 // the variable KDEDIR is set when KDE is running
818 const char *kdedir = getenv("KDEDIR");
819 if ( kdedir )
820 {
821 dirs.Add(wxString(kdedir) + _T("/share"));
822 icondirs.Add(wxString(kdedir) + _T("/share/icons/"));
823 }
824 else
825 {
826 // try to guess KDEDIR
827 dirs.Add(_T("/usr/share"));
828 dirs.Add(_T("/opt/kde/share"));
829 icondirs.Add(_T("/usr/share/icons/"));
830 icondirs.Add(_T("/usr/X11R6/share/icons/")); // Debian/Corel linux
831 icondirs.Add(_T("/opt/kde/share/icons/"));
832 }
833
834 size_t nDirs = dirs.GetCount();
835 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
836 {
837 LoadLinkFilesFromDir(dirs[nDir], icondirs);
838 }
839
840 m_inited = TRUE;
841 }
842
843 bool wxKDEIconHandler::GetIcon(const wxString& mimetype,
844 wxIcon * WXUNUSED_UNLESS_GUI(icon))
845 {
846 if ( !m_inited )
847 {
848 Init();
849 }
850
851 int index = ms_mimetypes.Index(mimetype);
852 if ( index == wxNOT_FOUND )
853 return FALSE;
854
855 wxString iconname = ms_icons[(size_t)index];
856
857 #if wxUSE_GUI
858 wxLogNull nolog;
859 wxIcon icn;
860 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
861 icn = wxIcon(iconname);
862 else
863 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
864
865 if ( !icn.Ok() )
866 return FALSE;
867
868 if ( icon )
869 *icon = icn;
870 #else
871 // helpful for testing in console mode
872 wxLogDebug(_T("Found KDE icon for '%s': '%s'\n"),
873 mimetype.c_str(), iconname.c_str());
874 #endif
875
876 return TRUE;
877 }
878
879
880 void wxKDEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
881 {
882 if ( !m_inited ) Init();
883
884 size_t cnt = ms_infoTypes.GetCount();
885 for (unsigned i = 0; i < cnt; i++)
886 manager -> AddMimeTypeInfo(ms_infoTypes[i], ms_infoExtensions[i], ms_infoDescriptions[i]);
887 }
888
889
890 // ----------------------------------------------------------------------------
891 // wxFileTypeImpl (Unix)
892 // ----------------------------------------------------------------------------
893
894 MailCapEntry *
895 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const
896 {
897 wxString command;
898 MailCapEntry *entry = m_manager->m_aEntries[m_index[0]];
899 while ( entry != NULL ) {
900 // notice that an empty command would always succeed (it's ok)
901 command = wxFileType::ExpandCommand(entry->GetTestCmd(), params);
902
903 // suppress the command output
904 if ( !command.IsEmpty() )
905 {
906 if ( wxSystem(command) == 0 ) {
907 // ok, passed
908 wxLogTrace(TRACE_MIME,
909 wxT("Test '%s' for mime type '%s' succeeded."),
910 command.c_str(), params.GetMimeType().c_str());
911 break;
912 }
913 else {
914 wxLogTrace(TRACE_MIME,
915 wxT("Test '%s' for mime type '%s' failed."),
916 command.c_str(), params.GetMimeType().c_str());
917 }
918 }
919
920 entry = entry->GetNext();
921 }
922
923 return entry;
924 }
925
926 bool wxFileTypeImpl::GetIcon(wxIcon *icon) const
927 {
928 wxArrayString mimetypes;
929 GetMimeTypes(mimetypes);
930
931 ArrayIconHandlers& handlers = m_manager->GetIconHandlers();
932 size_t count = handlers.GetCount();
933 size_t counttypes = mimetypes.GetCount();
934 for ( size_t n = 0; n < count; n++ )
935 {
936 for ( size_t n2 = 0; n2 < counttypes; n2++ )
937 {
938 if ( handlers[n]->GetIcon(mimetypes[n2], icon) )
939 return TRUE;
940 }
941 }
942
943 return FALSE;
944 }
945
946
947 bool
948 wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
949 {
950 mimeTypes.Clear();
951 for (size_t i = 0; i < m_index.GetCount(); i++)
952 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
953 return TRUE;
954 }
955
956
957 bool
958 wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd,
959 const wxFileType::MessageParameters& params,
960 bool open) const
961 {
962 MailCapEntry *entry = GetEntry(params);
963 if ( entry == NULL ) {
964 // all tests failed...
965 return FALSE;
966 }
967
968 wxString cmd = open ? entry->GetOpenCmd() : entry->GetPrintCmd();
969 if ( cmd.IsEmpty() ) {
970 // may happen, especially for "print"
971 return FALSE;
972 }
973
974 *expandedCmd = wxFileType::ExpandCommand(cmd, params);
975 return TRUE;
976 }
977
978 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
979 {
980 wxString strExtensions = m_manager->GetExtension(m_index[0]);
981 extensions.Empty();
982
983 // one extension in the space or comma delimitid list
984 wxString strExt;
985 for ( const wxChar *p = strExtensions; ; p++ ) {
986 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') ) {
987 if ( !strExt.IsEmpty() ) {
988 extensions.Add(strExt);
989 strExt.Empty();
990 }
991 //else: repeated spaces (shouldn't happen, but it's not that
992 // important if it does happen)
993
994 if ( *p == wxT('\0') )
995 break;
996 }
997 else if ( *p == wxT('.') ) {
998 // remove the dot from extension (but only if it's the first char)
999 if ( !strExt.IsEmpty() ) {
1000 strExt += wxT('.');
1001 }
1002 //else: no, don't append it
1003 }
1004 else {
1005 strExt += *p;
1006 }
1007 }
1008
1009 return TRUE;
1010 }
1011
1012 // ----------------------------------------------------------------------------
1013 // wxMimeTypesManagerImpl (Unix)
1014 // ----------------------------------------------------------------------------
1015
1016 /* static */
1017 ArrayIconHandlers& wxMimeTypesManagerImpl::GetIconHandlers()
1018 {
1019 if ( ms_iconHandlers.GetCount() == 0 )
1020 {
1021 ms_iconHandlers.Add(&gs_iconHandlerGNOME);
1022 ms_iconHandlers.Add(&gs_iconHandlerKDE);
1023 }
1024
1025 return ms_iconHandlers;
1026 }
1027
1028 // read system and user mailcaps (TODO implement mime.types support)
1029 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1030 {
1031 // directories where we look for mailcap and mime.types by default
1032 // (taken from metamail(1) sources)
1033 static const wxChar *aStandardLocations[] =
1034 {
1035 wxT("/etc"),
1036 wxT("/usr/etc"),
1037 wxT("/usr/local/etc"),
1038 wxT("/etc/mail"),
1039 wxT("/usr/public/lib")
1040 };
1041
1042 // first read the system wide file(s)
1043 size_t n;
1044 for ( n = 0; n < WXSIZEOF(aStandardLocations); n++ ) {
1045 wxString dir = aStandardLocations[n];
1046
1047 wxString file = dir + wxT("/mailcap");
1048 if ( wxFile::Exists(file) ) {
1049 ReadMailcap(file);
1050 }
1051
1052 file = dir + wxT("/mime.types");
1053 if ( wxFile::Exists(file) ) {
1054 ReadMimeTypes(file);
1055 }
1056 }
1057
1058 wxString strHome = wxGetenv(wxT("HOME"));
1059
1060 // and now the users mailcap
1061 wxString strUserMailcap = strHome + wxT("/.mailcap");
1062 if ( wxFile::Exists(strUserMailcap) ) {
1063 ReadMailcap(strUserMailcap);
1064 }
1065
1066 // read the users mime.types
1067 wxString strUserMimeTypes = strHome + wxT("/.mime.types");
1068 if ( wxFile::Exists(strUserMimeTypes) ) {
1069 ReadMimeTypes(strUserMimeTypes);
1070 }
1071
1072 // read KDE/GNOME tables
1073 ArrayIconHandlers& handlers = GetIconHandlers();
1074 size_t count = handlers.GetCount();
1075 for ( size_t hn = 0; hn < count; hn++ )
1076 handlers[hn]->GetMimeInfoRecords(this);
1077 }
1078
1079
1080 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1081 {
1082 size_t cnt = m_aEntries.GetCount();
1083 for (size_t i = 0; i < cnt; i++) delete m_aEntries[i];
1084 }
1085
1086
1087 wxFileType *
1088 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1089 {
1090 wxFileType *fileType = NULL;
1091 size_t count = m_aExtensions.GetCount();
1092 for ( size_t n = 0; n < count; n++ ) {
1093 wxString extensions = m_aExtensions[n];
1094 while ( !extensions.IsEmpty() ) {
1095 wxString field = extensions.BeforeFirst(wxT(' '));
1096 extensions = extensions.AfterFirst(wxT(' '));
1097
1098 // consider extensions as not being case-sensitive
1099 if ( field.IsSameAs(ext, FALSE /* no case */) ) {
1100 // found
1101 if (fileType == NULL) fileType = new wxFileType;
1102 fileType->m_impl->Init(this, n);
1103 // adds this mime type to _list_ of mime types with this extension
1104 }
1105 }
1106 }
1107
1108 return fileType;
1109 }
1110
1111 wxFileType *
1112 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1113 {
1114 // mime types are not case-sensitive
1115 wxString mimetype(mimeType);
1116 mimetype.MakeLower();
1117
1118 // first look for an exact match
1119 int index = m_aTypes.Index(mimetype);
1120 if ( index == wxNOT_FOUND ) {
1121 // then try to find "text/*" as match for "text/plain" (for example)
1122 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1123 // the whole string - ok.
1124 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
1125
1126 size_t nCount = m_aTypes.Count();
1127 for ( size_t n = 0; n < nCount; n++ ) {
1128 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1129 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") ) {
1130 index = n;
1131 break;
1132 }
1133 }
1134 }
1135
1136 if ( index != wxNOT_FOUND ) {
1137 wxFileType *fileType = new wxFileType;
1138 fileType->m_impl->Init(this, index);
1139
1140 return fileType;
1141 }
1142 else {
1143 // not found...
1144 return NULL;
1145 }
1146 }
1147
1148 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1149 {
1150 wxString extensions;
1151 const wxArrayString& exts = filetype.GetExtensions();
1152 size_t nExts = exts.GetCount();
1153 for ( size_t nExt = 0; nExt < nExts; nExt++ ) {
1154 if ( nExt > 0 ) {
1155 extensions += wxT(' ');
1156 }
1157 extensions += exts[nExt];
1158 }
1159
1160 AddMimeTypeInfo(filetype.GetMimeType(),
1161 extensions,
1162 filetype.GetDescription());
1163
1164 AddMailcapInfo(filetype.GetMimeType(),
1165 filetype.GetOpenCommand(),
1166 filetype.GetPrintCommand(),
1167 wxT(""),
1168 filetype.GetDescription());
1169 }
1170
1171 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
1172 const wxString& strExtensions,
1173 const wxString& strDesc)
1174 {
1175 int index = m_aTypes.Index(strMimeType);
1176 if ( index == wxNOT_FOUND ) {
1177 // add a new entry
1178 m_aTypes.Add(strMimeType);
1179 m_aEntries.Add(NULL);
1180 m_aExtensions.Add(strExtensions);
1181 m_aDescriptions.Add(strDesc);
1182 }
1183 else {
1184 // modify an existing one
1185 if ( !strDesc.IsEmpty() ) {
1186 m_aDescriptions[index] = strDesc; // replace old value
1187 }
1188 m_aExtensions[index] += ' ' + strExtensions;
1189 }
1190 }
1191
1192 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
1193 const wxString& strOpenCmd,
1194 const wxString& strPrintCmd,
1195 const wxString& strTest,
1196 const wxString& strDesc)
1197 {
1198 MailCapEntry *entry = new MailCapEntry(strOpenCmd, strPrintCmd, strTest);
1199
1200 int nIndex = m_aTypes.Index(strType);
1201 if ( nIndex == wxNOT_FOUND ) {
1202 // new file type
1203 m_aTypes.Add(strType);
1204
1205 m_aEntries.Add(entry);
1206 m_aExtensions.Add(wxT(""));
1207 m_aDescriptions.Add(strDesc);
1208 }
1209 else {
1210 // always append the entry in the tail of the list - info added with
1211 // this function can only come from AddFallbacks()
1212 MailCapEntry *entryOld = m_aEntries[nIndex];
1213 if ( entryOld )
1214 entry->Append(entryOld);
1215 else
1216 m_aEntries[nIndex] = entry;
1217 }
1218 }
1219
1220 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
1221 {
1222 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
1223 strFileName.c_str());
1224
1225 wxTextFile file(strFileName);
1226 if ( !file.Open() )
1227 return FALSE;
1228
1229 // the information we extract
1230 wxString strMimeType, strDesc, strExtensions;
1231
1232 size_t nLineCount = file.GetLineCount();
1233 const wxChar *pc = NULL;
1234 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1235 if ( pc == NULL ) {
1236 // now we're at the start of the line
1237 pc = file[nLine].c_str();
1238 }
1239 else {
1240 // we didn't finish with the previous line yet
1241 nLine--;
1242 }
1243
1244 // skip whitespace
1245 while ( wxIsspace(*pc) )
1246 pc++;
1247
1248 // comment or blank line?
1249 if ( *pc == wxT('#') || !*pc ) {
1250 // skip the whole line
1251 pc = NULL;
1252 continue;
1253 }
1254
1255 // detect file format
1256 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
1257 if ( pEqualSign == NULL ) {
1258 // brief format
1259 // ------------
1260
1261 // first field is mime type
1262 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ ) {
1263 strMimeType += *pc;
1264 }
1265
1266 // skip whitespace
1267 while ( wxIsspace(*pc) )
1268 pc++;
1269
1270 // take all the rest of the string
1271 strExtensions = pc;
1272
1273 // no description...
1274 strDesc.Empty();
1275 }
1276 else {
1277 // expanded format
1278 // ---------------
1279
1280 // the string on the left of '=' is the field name
1281 wxString strLHS(pc, pEqualSign - pc);
1282
1283 // eat whitespace
1284 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
1285 ;
1286
1287 const wxChar *pEnd;
1288 if ( *pc == wxT('"') ) {
1289 // the string is quoted and ends at the matching quote
1290 pEnd = wxStrchr(++pc, wxT('"'));
1291 if ( pEnd == NULL ) {
1292 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1293 "quoted string."),
1294 strFileName.c_str(), nLine + 1);
1295 }
1296 }
1297 else {
1298 // unquoted string ends at the first space or at the end of
1299 // line
1300 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
1301 ;
1302 }
1303
1304 // now we have the RHS (field value)
1305 wxString strRHS(pc, pEnd - pc);
1306
1307 // check what follows this entry
1308 if ( *pEnd == wxT('"') ) {
1309 // skip this quote
1310 pEnd++;
1311 }
1312
1313 for ( pc = pEnd; wxIsspace(*pc); pc++ )
1314 ;
1315
1316 // if there is something left, it may be either a '\\' to continue
1317 // the line or the next field of the same entry
1318 bool entryEnded = *pc == wxT('\0'),
1319 nextFieldOnSameLine = FALSE;
1320 if ( !entryEnded ) {
1321 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
1322 }
1323
1324 // now see what we got
1325 if ( strLHS == wxT("type") ) {
1326 strMimeType = strRHS;
1327 }
1328 else if ( strLHS == wxT("desc") ) {
1329 strDesc = strRHS;
1330 }
1331 else if ( strLHS == wxT("exts") ) {
1332 strExtensions = strRHS;
1333 }
1334 else {
1335 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1336 strFileName.c_str(), nLine + 1, strLHS.c_str());
1337 }
1338
1339 if ( !entryEnded ) {
1340 if ( !nextFieldOnSameLine )
1341 pc = NULL;
1342 //else: don't reset it
1343
1344 // as we don't reset strMimeType, the next field in this entry
1345 // will be interpreted correctly.
1346
1347 continue;
1348 }
1349 }
1350
1351 // although it doesn't seem to be covered by RFCs, some programs
1352 // (notably Netscape) create their entries with several comma
1353 // separated extensions (RFC mention the spaces only)
1354 strExtensions.Replace(wxT(","), wxT(" "));
1355
1356 // also deal with the leading dot
1357 if ( !strExtensions.IsEmpty() && strExtensions[0u] == wxT('.') )
1358 {
1359 strExtensions.erase(0, 1);
1360 }
1361
1362 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
1363
1364 // finished with this line
1365 pc = NULL;
1366 }
1367
1368 // check our data integriry
1369 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1370 m_aTypes.Count() == m_aExtensions.Count() &&
1371 m_aTypes.Count() == m_aDescriptions.Count() );
1372
1373 return TRUE;
1374 }
1375
1376 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
1377 bool fallback)
1378 {
1379 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
1380 strFileName.c_str());
1381
1382 wxTextFile file(strFileName);
1383 if ( !file.Open() )
1384 return FALSE;
1385
1386 // see the comments near the end of function for the reason we need these
1387 // variables (search for the next occurence of them)
1388 // indices of MIME types (in m_aTypes) we already found in this file
1389 wxArrayInt aEntryIndices;
1390 // aLastIndices[n] is the index of last element in
1391 // m_aEntries[aEntryIndices[n]] from this file
1392 wxArrayInt aLastIndices;
1393
1394 size_t nLineCount = file.GetLineCount();
1395 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1396 // now we're at the start of the line
1397 const wxChar *pc = file[nLine].c_str();
1398
1399 // skip whitespace
1400 while ( wxIsspace(*pc) )
1401 pc++;
1402
1403 // comment or empty string?
1404 if ( *pc == wxT('#') || *pc == wxT('\0') )
1405 continue;
1406
1407 // no, do parse
1408
1409 // what field are we currently in? The first 2 are fixed and there may
1410 // be an arbitrary number of other fields -- currently, we are not
1411 // interested in any of them, but we should parse them as well...
1412 enum
1413 {
1414 Field_Type,
1415 Field_OpenCmd,
1416 Field_Other
1417 } currentToken = Field_Type;
1418
1419 // the flags and field values on the current line
1420 bool needsterminal = FALSE,
1421 copiousoutput = FALSE;
1422 wxString strType,
1423 strOpenCmd,
1424 strPrintCmd,
1425 strTest,
1426 strDesc,
1427 curField; // accumulator
1428 for ( bool cont = TRUE; cont; pc++ ) {
1429 switch ( *pc ) {
1430 case wxT('\\'):
1431 // interpret the next character literally (notice that
1432 // backslash can be used for line continuation)
1433 if ( *++pc == wxT('\0') ) {
1434 // fetch the next line.
1435
1436 // pc currently points to nowhere, but after the next
1437 // pc++ in the for line it will point to the beginning
1438 // of the next line in the file
1439 pc = file[++nLine].c_str() - 1;
1440 }
1441 else {
1442 // just a normal character
1443 curField += *pc;
1444 }
1445 break;
1446
1447 case wxT('\0'):
1448 cont = FALSE; // end of line reached, exit the loop
1449
1450 // fall through
1451
1452 case wxT(';'):
1453 // store this field and start looking for the next one
1454
1455 // trim whitespaces from both sides
1456 curField.Trim(TRUE).Trim(FALSE);
1457
1458 switch ( currentToken ) {
1459 case Field_Type:
1460 strType = curField;
1461 if ( strType.Find(wxT('/')) == wxNOT_FOUND ) {
1462 // we interpret "type" as "type/*"
1463 strType += wxT("/*");
1464 }
1465
1466 currentToken = Field_OpenCmd;
1467 break;
1468
1469 case Field_OpenCmd:
1470 strOpenCmd = curField;
1471
1472 currentToken = Field_Other;
1473 break;
1474
1475 case Field_Other:
1476 {
1477 // "good" mailcap entry?
1478 bool ok = TRUE;
1479
1480 // is this something of the form foo=bar?
1481 const wxChar *pEq = wxStrchr(curField, wxT('='));
1482 if ( pEq != NULL ) {
1483 wxString lhs = curField.BeforeFirst(wxT('=')),
1484 rhs = curField.AfterFirst(wxT('='));
1485
1486 lhs.Trim(TRUE); // from right
1487 rhs.Trim(FALSE); // from left
1488
1489 if ( lhs == wxT("print") )
1490 strPrintCmd = rhs;
1491 else if ( lhs == wxT("test") )
1492 strTest = rhs;
1493 else if ( lhs == wxT("description") ) {
1494 // it might be quoted
1495 if ( rhs[0u] == wxT('"') &&
1496 rhs.Last() == wxT('"') ) {
1497 strDesc = wxString(rhs.c_str() + 1,
1498 rhs.Len() - 2);
1499 }
1500 else {
1501 strDesc = rhs;
1502 }
1503 }
1504 else if ( lhs == wxT("compose") ||
1505 lhs == wxT("composetyped") ||
1506 lhs == wxT("notes") ||
1507 lhs == wxT("edit") )
1508 ; // ignore
1509 else
1510 ok = FALSE;
1511
1512 }
1513 else {
1514 // no, it's a simple flag
1515 // TODO support the flags:
1516 // 1. create an xterm for 'needsterminal'
1517 // 2. append "| $PAGER" for 'copiousoutput'
1518 if ( curField == wxT("needsterminal") )
1519 needsterminal = TRUE;
1520 else if ( curField == wxT("copiousoutput") )
1521 copiousoutput = TRUE;
1522 else if ( curField == wxT("textualnewlines") )
1523 ; // ignore
1524 else
1525 ok = FALSE;
1526 }
1527
1528 if ( !ok )
1529 {
1530 if ( !IsKnownUnimportantField(curField) )
1531 {
1532 // don't flood the user with error
1533 // messages if we don't understand
1534 // something in his mailcap, but give
1535 // them in debug mode because this might
1536 // be useful for the programmer
1537 wxLogDebug
1538 (
1539 wxT("Mailcap file %s, line %d: "
1540 "unknown field '%s' for the "
1541 "MIME type '%s' ignored."),
1542 strFileName.c_str(),
1543 nLine + 1,
1544 curField.c_str(),
1545 strType.c_str()
1546 );
1547 }
1548 }
1549 }
1550
1551 // it already has this value
1552 //currentToken = Field_Other;
1553 break;
1554
1555 default:
1556 wxFAIL_MSG(wxT("unknown field type in mailcap"));
1557 }
1558
1559 // next token starts immediately after ';'
1560 curField.Empty();
1561 break;
1562
1563 default:
1564 curField += *pc;
1565 }
1566 }
1567
1568 // check that we really read something reasonable
1569 if ( currentToken == Field_Type || currentToken == Field_OpenCmd ) {
1570 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1571 "ignored."),
1572 strFileName.c_str(), nLine + 1);
1573 }
1574 else {
1575 MailCapEntry *entry = new MailCapEntry(strOpenCmd,
1576 strPrintCmd,
1577 strTest);
1578
1579 // NB: because of complications below (we must get entries priority
1580 // right), we can't use AddMailcapInfo() here, unfortunately.
1581 strType.MakeLower();
1582 int nIndex = m_aTypes.Index(strType);
1583 if ( nIndex == wxNOT_FOUND ) {
1584 // new file type
1585 m_aTypes.Add(strType);
1586
1587 m_aEntries.Add(entry);
1588 m_aExtensions.Add(wxT(""));
1589 m_aDescriptions.Add(strDesc);
1590 }
1591 else {
1592 // modify the existing entry: the entries in one and the same
1593 // file are read in top-to-bottom order, i.e. the entries read
1594 // first should be tried before the entries below. However,
1595 // the files read later should override the settings in the
1596 // files read before (except if fallback is TRUE), thus we
1597 // Insert() the new entry to the list if it has already
1598 // occured in _this_ file, but Prepend() it if it occured in
1599 // some of the previous ones and Append() to it in the
1600 // fallback case
1601
1602 if ( fallback ) {
1603 // 'fallback' parameter prevents the entries from this
1604 // file from overriding the other ones - always append
1605 MailCapEntry *entryOld = m_aEntries[nIndex];
1606 if ( entryOld )
1607 entry->Append(entryOld);
1608 else
1609 m_aEntries[nIndex] = entry;
1610 }
1611 else {
1612 int entryIndex = aEntryIndices.Index(nIndex);
1613 if ( entryIndex == wxNOT_FOUND ) {
1614 // first time in this file
1615 aEntryIndices.Add(nIndex);
1616 aLastIndices.Add(0);
1617
1618 entry->Prepend(m_aEntries[nIndex]);
1619 m_aEntries[nIndex] = entry;
1620 }
1621 else {
1622 // not the first time in _this_ file
1623 size_t nEntryIndex = (size_t)entryIndex;
1624 MailCapEntry *entryOld = m_aEntries[nIndex];
1625 if ( entryOld )
1626 entry->Insert(entryOld, aLastIndices[nEntryIndex]);
1627 else
1628 m_aEntries[nIndex] = entry;
1629
1630 // the indices were shifted by 1
1631 aLastIndices[nEntryIndex]++;
1632 }
1633 }
1634
1635 if ( !strDesc.IsEmpty() ) {
1636 // replace the old one - what else can we do??
1637 m_aDescriptions[nIndex] = strDesc;
1638 }
1639 }
1640 }
1641
1642 // check our data integriry
1643 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1644 m_aTypes.Count() == m_aExtensions.Count() &&
1645 m_aTypes.Count() == m_aDescriptions.Count() );
1646 }
1647
1648 return TRUE;
1649 }
1650
1651 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1652 {
1653 mimetypes.Empty();
1654
1655 wxString type;
1656 size_t count = m_aTypes.GetCount();
1657 for ( size_t n = 0; n < count; n++ )
1658 {
1659 // don't return template types from here (i.e. anything containg '*')
1660 type = m_aTypes[n];
1661 if ( type.Find(_T('*')) == wxNOT_FOUND )
1662 {
1663 mimetypes.Add(type);
1664 }
1665 }
1666
1667 return mimetypes.GetCount();
1668 }
1669
1670 // ----------------------------------------------------------------------------
1671 // private functions
1672 // ----------------------------------------------------------------------------
1673
1674 static bool IsKnownUnimportantField(const wxString& fieldAll)
1675 {
1676 static const wxChar *knownFields[] =
1677 {
1678 _T("x-mozilla-flags"),
1679 _T("nametemplate"),
1680 _T("textualnewlines"),
1681 };
1682
1683 wxString field = fieldAll.BeforeFirst(_T('='));
1684 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
1685 {
1686 if ( field.CmpNoCase(knownFields[n]) == 0 )
1687 return TRUE;
1688 }
1689
1690 return FALSE;
1691 }
1692
1693 #endif
1694 // wxUSE_FILE && wxUSE_TEXTFILE
1695