fixed a bug in handling mailcap entries with more than one commands with tests
[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 // Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
327 // just before the field name.
328
329 void wxGNOMEIconHandler::LoadIconsFromKeyFile(const wxString& filename)
330 {
331 wxTextFile textfile(filename);
332 if ( !textfile.Open() )
333 return;
334
335 // values for the entry being parsed
336 wxString curMimeType, curIconFile;
337
338 const wxChar *pc;
339 size_t nLineCount = textfile.GetLineCount();
340 for ( size_t nLine = 0; ; nLine++ )
341 {
342 if ( nLine < nLineCount )
343 {
344 pc = textfile[nLine].c_str();
345 if ( *pc == _T('#') )
346 {
347 // skip comments
348 continue;
349 }
350 }
351 else
352 {
353 // so that we will fall into the "if" below
354 pc = NULL;
355 }
356
357 if ( !pc || !*pc )
358 {
359 // end of the entry
360 if ( !!curMimeType && !!curIconFile )
361 {
362 // do we already know this mimetype?
363 int i = ms_mimetypes.Index(curMimeType);
364 if ( i == wxNOT_FOUND )
365 {
366 // add a new entry
367 size_t n = ms_mimetypes.Add(curMimeType);
368 ms_icons.Insert(curIconFile, n);
369 }
370 else
371 {
372 // replace the existing one (this means that the directories
373 // should be searched in order of increased priority!)
374 ms_icons[(size_t)i] = curIconFile;
375 }
376 }
377
378 if ( !pc )
379 {
380 // the end - this can only happen if nLine == nLineCount
381 break;
382 }
383
384 curIconFile.Empty();
385
386 continue;
387 }
388
389 // what do we have here?
390 if ( *pc == _T('\t') )
391 {
392 // this is a field=value ling
393 pc++; // skip leading TAB
394
395 // skip optional "[lang]"
396 if ( *pc == _T('[') )
397 {
398 while ( *pc )
399 {
400 if ( *pc++ == _T(']') )
401 break;
402 }
403 }
404
405 static const int lenField = 13; // strlen("icon-filename")
406 if ( wxStrncmp(pc, _T("icon-filename"), lenField) == 0 )
407 {
408 // skip '=' which follows and take everything left until the end
409 // of line
410 curIconFile = pc + lenField + 1;
411 }
412 //else: some other field, we don't care
413 }
414 else
415 {
416 // this is the start of the new section
417 curMimeType.Empty();
418
419 while ( *pc != _T(':') && *pc != _T('\0') )
420 {
421 curMimeType += *pc++;
422 }
423 }
424 }
425 }
426
427 void wxGNOMEIconHandler::LoadKeyFilesFromDir(const wxString& dirbase)
428 {
429 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
430 _T("base directory shouldn't end with a slash") );
431
432 wxString dirname = dirbase;
433 dirname << _T("/mime-info");
434
435 if ( !wxDir::Exists(dirname) )
436 return;
437
438 wxDir dir(dirname);
439 if ( !dir.IsOpened() )
440 return;
441
442 // we will concatenate it with filename to get the full path below
443 dirname += _T('/');
444
445 wxString filename;
446 bool cont = dir.GetFirst(&filename, _T("*.keys"), wxDIR_FILES);
447 while ( cont )
448 {
449 LoadIconsFromKeyFile(dirname + filename);
450
451 cont = dir.GetNext(&filename);
452 }
453 }
454
455
456 void wxGNOMEIconHandler::LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager)
457 {
458 wxTextFile textfile(filename);
459 if ( !textfile.Open() )
460 return;
461
462 // values for the entry being parsed
463 wxString curMimeType, curExtList;
464
465 const wxChar *pc;
466 size_t nLineCount = textfile.GetLineCount();
467 for ( size_t nLine = 0; ; nLine++ )
468 {
469 if ( nLine < nLineCount )
470 {
471 pc = textfile[nLine].c_str();
472 if ( *pc == _T('#') )
473 {
474 // skip comments
475 continue;
476 }
477 }
478 else
479 {
480 // so that we will fall into the "if" below
481 pc = NULL;
482 }
483
484 if ( !pc || !*pc )
485 {
486 // end of the entry
487 if ( !!curMimeType && !!curExtList )
488 {
489 manager -> AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
490 }
491
492 if ( !pc )
493 {
494 // the end - this can only happen if nLine == nLineCount
495 break;
496 }
497
498 curExtList.Empty();
499
500 continue;
501 }
502
503 // what do we have here?
504 if ( *pc == _T('\t') )
505 {
506 // this is a field=value ling
507 pc++; // skip leading TAB
508
509 static const int lenField = 4; // strlen("ext:")
510 if ( wxStrncmp(pc, _T("ext:"), lenField) == 0 )
511 {
512 // skip ' ' which follows and take everything left until the end
513 // of line
514 curExtList = pc + lenField + 1;
515 }
516 //else: some other field, we don't care
517 }
518 else
519 {
520 // this is the start of the new section
521 curMimeType.Empty();
522
523 while ( *pc != _T(':') && *pc != _T('\0') )
524 {
525 curMimeType += *pc++;
526 }
527 }
528 }
529 }
530
531
532 void wxGNOMEIconHandler::LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager)
533 {
534 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
535 _T("base directory shouldn't end with a slash") );
536
537 wxString dirname = dirbase;
538 dirname << _T("/mime-info");
539
540 if ( !wxDir::Exists(dirname) )
541 return;
542
543 wxDir dir(dirname);
544 if ( !dir.IsOpened() )
545 return;
546
547 // we will concatenate it with filename to get the full path below
548 dirname += _T('/');
549
550 wxString filename;
551 bool cont = dir.GetFirst(&filename, _T("*.mime"), wxDIR_FILES);
552 while ( cont )
553 {
554 LoadMimeTypesFromMimeFile(dirname + filename, manager);
555
556 cont = dir.GetNext(&filename);
557 }
558 }
559
560
561 void wxGNOMEIconHandler::Init()
562 {
563 wxArrayString dirs;
564 dirs.Add(_T("/usr/share"));
565 dirs.Add(_T("/usr/local/share"));
566
567 wxString gnomedir;
568 wxGetHomeDir( &gnomedir );
569 gnomedir += _T("/.gnome");
570 dirs.Add( gnomedir );
571
572 size_t nDirs = dirs.GetCount();
573 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
574 {
575 LoadKeyFilesFromDir(dirs[nDir]);
576 }
577
578 m_inited = TRUE;
579 }
580
581
582 void wxGNOMEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
583 {
584 if ( !m_inited )
585 {
586 Init();
587 }
588
589 wxArrayString dirs;
590 dirs.Add(_T("/usr/share"));
591 dirs.Add(_T("/usr/local/share"));
592
593 wxString gnomedir;
594 wxGetHomeDir( &gnomedir );
595 gnomedir += _T("/.gnome");
596 dirs.Add( gnomedir );
597
598 size_t nDirs = dirs.GetCount();
599 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
600 {
601 LoadMimeFilesFromDir(dirs[nDir], manager);
602 }
603 }
604
605 #if wxUSE_GUI
606 #define WXUNUSED_UNLESS_GUI(p) p
607 #else
608 #define WXUNUSED_UNLESS_GUI(p)
609 #endif
610
611 bool wxGNOMEIconHandler::GetIcon(const wxString& mimetype,
612 wxIcon * WXUNUSED_UNLESS_GUI(icon))
613 {
614 if ( !m_inited )
615 {
616 Init();
617 }
618
619 int index = ms_mimetypes.Index(mimetype);
620 if ( index == wxNOT_FOUND )
621 return FALSE;
622
623 wxString iconname = ms_icons[(size_t)index];
624
625 #if wxUSE_GUI
626 wxLogNull nolog;
627 wxIcon icn;
628 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
629 icn = wxIcon(iconname);
630 else
631 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
632 if ( !icn.Ok() )
633 return FALSE;
634
635 if ( icon )
636 *icon = icn;
637 #else
638 // helpful for testing in console mode
639 wxLogDebug(_T("Found GNOME icon for '%s': '%s'\n"),
640 mimetype.c_str(), iconname.c_str());
641 #endif
642
643 return TRUE;
644 }
645
646 // ----------------------------------------------------------------------------
647 // wxKDEIconHandler
648 // ----------------------------------------------------------------------------
649
650 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
651 // may be found in either of the following locations
652 //
653 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
654 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
655 //
656 // The format of a .kdelnk file is almost the same as the one used by
657 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
658 // value for the entry "Type"
659
660 void wxKDEIconHandler::LoadLinksForMimeSubtype(const wxString& dirbase,
661 const wxString& subdir,
662 const wxString& filename,
663 const wxArrayString& icondirs)
664 {
665 wxFFile file(dirbase + filename);
666 if ( !file.IsOpened() )
667 return;
668
669 // construct mimetype from the directory name and the basename of the
670 // file (it always has .kdelnk extension)
671 wxString mimetype;
672 mimetype << subdir << _T('/') << filename.BeforeLast(_T('.'));
673
674 // these files are small, slurp the entire file at once
675 wxString text;
676 if ( !file.ReadAll(&text) )
677 return;
678
679 int pos;
680 const wxChar *pc;
681
682 // before trying to find an icon, grab mimetype information
683 // (because BFU's machine would hardly have well-edited mime.types but (s)he might
684 // have edited it in control panel...)
685
686 wxString mime_extension, mime_desc;
687
688 pos = wxNOT_FOUND;
689 if (wxGetLocale() != NULL)
690 mime_desc = _T("Comment[") + wxGetLocale()->GetName() + _T("]=");
691 if (pos == wxNOT_FOUND) mime_desc = _T("Comment=");
692 pos = text.Find(mime_desc);
693 if (pos == wxNOT_FOUND) mime_desc = wxEmptyString;
694 else
695 {
696 pc = text.c_str() + pos + mime_desc.Length();
697 mime_desc = wxEmptyString;
698 while ( *pc && *pc != _T('\n') ) mime_desc += *pc++;
699 }
700
701 pos = text.Find(_T("Patterns="));
702 if (pos != wxNOT_FOUND)
703 {
704 wxString exts;
705 pc = text.c_str() + pos + 9;
706 while ( *pc && *pc != _T('\n') ) exts += *pc++;
707 wxStringTokenizer tokenizer(exts, _T(";"));
708 wxString e;
709
710 while (tokenizer.HasMoreTokens())
711 {
712 e = tokenizer.GetNextToken();
713 if (e.Left(2) != _T("*.")) continue; // don't support too difficult patterns
714 mime_extension << e.Mid(2);
715 mime_extension << _T(' ');
716 }
717 mime_extension.RemoveLast();
718 }
719
720 ms_infoTypes.Add(mimetype);
721 ms_infoDescriptions.Add(mime_desc);
722 ms_infoExtensions.Add(mime_extension);
723
724 // ok, now we can take care of icon:
725
726 pos = text.Find(_T("Icon="));
727 if ( pos == wxNOT_FOUND )
728 {
729 // no icon info
730 return;
731 }
732
733 wxString icon;
734
735 pc = text.c_str() + pos + 5; // 5 == strlen("Icon=")
736 while ( *pc && *pc != _T('\n') )
737 {
738 icon += *pc++;
739 }
740
741 if ( !!icon )
742 {
743 // we must check if the file exists because it may be stored
744 // in many locations, at least ~/.kde and $KDEDIR
745 size_t nDir, nDirs = icondirs.GetCount();
746 for ( nDir = 0; nDir < nDirs; nDir++ )
747 if (wxFileExists(icondirs[nDir] + icon))
748 {
749 icon.Prepend(icondirs[nDir]);
750 break;
751 }
752 if (nDir == nDirs) return; //does not exist
753
754 // do we already have this MIME type?
755 int i = ms_mimetypes.Index(mimetype);
756 if ( i == wxNOT_FOUND )
757 {
758 // add it
759 size_t n = ms_mimetypes.Add(mimetype);
760 ms_icons.Insert(icon, n);
761 }
762 else
763 {
764 // replace the old value
765 ms_icons[(size_t)i] = icon;
766 }
767 }
768 }
769
770 void wxKDEIconHandler::LoadLinksForMimeType(const wxString& dirbase,
771 const wxString& subdir,
772 const wxArrayString& icondirs)
773 {
774 wxString dirname = dirbase;
775 dirname += subdir;
776 wxDir dir(dirname);
777 if ( !dir.IsOpened() )
778 return;
779
780 dirname += _T('/');
781
782 wxString filename;
783 bool cont = dir.GetFirst(&filename, _T("*.kdelnk"), wxDIR_FILES);
784 while ( cont )
785 {
786 LoadLinksForMimeSubtype(dirname, subdir, filename, icondirs);
787
788 cont = dir.GetNext(&filename);
789 }
790 }
791
792 void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString& dirbase,
793 const wxArrayString& icondirs)
794 {
795 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
796 _T("base directory shouldn't end with a slash") );
797
798 wxString dirname = dirbase;
799 dirname << _T("/mimelnk");
800
801 if ( !wxDir::Exists(dirname) )
802 return;
803
804 wxDir dir(dirname);
805 if ( !dir.IsOpened() )
806 return;
807
808 // we will concatenate it with dir name to get the full path below
809 dirname += _T('/');
810
811 wxString subdir;
812 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
813 while ( cont )
814 {
815 LoadLinksForMimeType(dirname, subdir, icondirs);
816
817 cont = dir.GetNext(&subdir);
818 }
819 }
820
821 void wxKDEIconHandler::Init()
822 {
823 wxArrayString dirs;
824 wxArrayString icondirs;
825
826 // settings in ~/.kde have maximal priority
827 dirs.Add(wxGetHomeDir() + _T("/.kde/share"));
828 icondirs.Add(wxGetHomeDir() + _T("/.kde/share/icons/"));
829
830 // the variable KDEDIR is set when KDE is running
831 const char *kdedir = getenv("KDEDIR");
832 if ( kdedir )
833 {
834 dirs.Add(wxString(kdedir) + _T("/share"));
835 icondirs.Add(wxString(kdedir) + _T("/share/icons/"));
836 }
837 else
838 {
839 // try to guess KDEDIR
840 dirs.Add(_T("/usr/share"));
841 dirs.Add(_T("/opt/kde/share"));
842 icondirs.Add(_T("/usr/share/icons/"));
843 icondirs.Add(_T("/usr/X11R6/share/icons/")); // Debian/Corel linux
844 icondirs.Add(_T("/opt/kde/share/icons/"));
845 }
846
847 size_t nDirs = dirs.GetCount();
848 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
849 {
850 LoadLinkFilesFromDir(dirs[nDir], icondirs);
851 }
852
853 m_inited = TRUE;
854 }
855
856 bool wxKDEIconHandler::GetIcon(const wxString& mimetype,
857 wxIcon * WXUNUSED_UNLESS_GUI(icon))
858 {
859 if ( !m_inited )
860 {
861 Init();
862 }
863
864 int index = ms_mimetypes.Index(mimetype);
865 if ( index == wxNOT_FOUND )
866 return FALSE;
867
868 wxString iconname = ms_icons[(size_t)index];
869
870 #if wxUSE_GUI
871 wxLogNull nolog;
872 wxIcon icn;
873 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
874 icn = wxIcon(iconname);
875 else
876 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
877
878 if ( !icn.Ok() )
879 return FALSE;
880
881 if ( icon )
882 *icon = icn;
883 #else
884 // helpful for testing in console mode
885 wxLogDebug(_T("Found KDE icon for '%s': '%s'\n"),
886 mimetype.c_str(), iconname.c_str());
887 #endif
888
889 return TRUE;
890 }
891
892
893 void wxKDEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
894 {
895 if ( !m_inited ) Init();
896
897 size_t cnt = ms_infoTypes.GetCount();
898 for (unsigned i = 0; i < cnt; i++)
899 manager -> AddMimeTypeInfo(ms_infoTypes[i], ms_infoExtensions[i], ms_infoDescriptions[i]);
900 }
901
902
903 // ----------------------------------------------------------------------------
904 // wxFileTypeImpl (Unix)
905 // ----------------------------------------------------------------------------
906
907 MailCapEntry *
908 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const
909 {
910 wxString command;
911 MailCapEntry *entry = m_manager->m_aEntries[m_index[0]];
912 while ( entry != NULL ) {
913 // get the command to run as the test for this entry
914 command = wxFileType::ExpandCommand(entry->GetTestCmd(), params);
915
916 // don't trace the test result if there is no test at all
917 if ( command.IsEmpty() )
918 {
919 // no test at all, ok
920 break;
921 }
922
923 if ( wxSystem(command) == 0 ) {
924 // ok, test passed
925 wxLogTrace(TRACE_MIME,
926 wxT("Test '%s' for mime type '%s' succeeded."),
927 command.c_str(), params.GetMimeType().c_str());
928 break;
929 }
930 else {
931 wxLogTrace(TRACE_MIME,
932 wxT("Test '%s' for mime type '%s' failed."),
933 command.c_str(), params.GetMimeType().c_str());
934 }
935
936 entry = entry->GetNext();
937 }
938
939 return entry;
940 }
941
942 bool wxFileTypeImpl::GetIcon(wxIcon *icon) const
943 {
944 wxArrayString mimetypes;
945 GetMimeTypes(mimetypes);
946
947 ArrayIconHandlers& handlers = m_manager->GetIconHandlers();
948 size_t count = handlers.GetCount();
949 size_t counttypes = mimetypes.GetCount();
950 for ( size_t n = 0; n < count; n++ )
951 {
952 for ( size_t n2 = 0; n2 < counttypes; n2++ )
953 {
954 if ( handlers[n]->GetIcon(mimetypes[n2], icon) )
955 return TRUE;
956 }
957 }
958
959 return FALSE;
960 }
961
962
963 bool
964 wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
965 {
966 mimeTypes.Clear();
967 for (size_t i = 0; i < m_index.GetCount(); i++)
968 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
969 return TRUE;
970 }
971
972
973 bool
974 wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd,
975 const wxFileType::MessageParameters& params,
976 bool open) const
977 {
978 MailCapEntry *entry = GetEntry(params);
979 if ( entry == NULL ) {
980 // all tests failed...
981 return FALSE;
982 }
983
984 wxString cmd = open ? entry->GetOpenCmd() : entry->GetPrintCmd();
985 if ( cmd.IsEmpty() ) {
986 // may happen, especially for "print"
987 return FALSE;
988 }
989
990 *expandedCmd = wxFileType::ExpandCommand(cmd, params);
991 return TRUE;
992 }
993
994 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
995 {
996 wxString strExtensions = m_manager->GetExtension(m_index[0]);
997 extensions.Empty();
998
999 // one extension in the space or comma delimitid list
1000 wxString strExt;
1001 for ( const wxChar *p = strExtensions; ; p++ ) {
1002 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') ) {
1003 if ( !strExt.IsEmpty() ) {
1004 extensions.Add(strExt);
1005 strExt.Empty();
1006 }
1007 //else: repeated spaces (shouldn't happen, but it's not that
1008 // important if it does happen)
1009
1010 if ( *p == wxT('\0') )
1011 break;
1012 }
1013 else if ( *p == wxT('.') ) {
1014 // remove the dot from extension (but only if it's the first char)
1015 if ( !strExt.IsEmpty() ) {
1016 strExt += wxT('.');
1017 }
1018 //else: no, don't append it
1019 }
1020 else {
1021 strExt += *p;
1022 }
1023 }
1024
1025 return TRUE;
1026 }
1027
1028 // ----------------------------------------------------------------------------
1029 // wxMimeTypesManagerImpl (Unix)
1030 // ----------------------------------------------------------------------------
1031
1032 /* static */
1033 ArrayIconHandlers& wxMimeTypesManagerImpl::GetIconHandlers()
1034 {
1035 if ( ms_iconHandlers.GetCount() == 0 )
1036 {
1037 ms_iconHandlers.Add(&gs_iconHandlerGNOME);
1038 ms_iconHandlers.Add(&gs_iconHandlerKDE);
1039 }
1040
1041 return ms_iconHandlers;
1042 }
1043
1044 // read system and user mailcaps (TODO implement mime.types support)
1045 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1046 {
1047 // directories where we look for mailcap and mime.types by default
1048 // (taken from metamail(1) sources)
1049 static const wxChar *aStandardLocations[] =
1050 {
1051 wxT("/etc"),
1052 wxT("/usr/etc"),
1053 wxT("/usr/local/etc"),
1054 wxT("/etc/mail"),
1055 wxT("/usr/public/lib")
1056 };
1057
1058 // first read the system wide file(s)
1059 size_t n;
1060 for ( n = 0; n < WXSIZEOF(aStandardLocations); n++ ) {
1061 wxString dir = aStandardLocations[n];
1062
1063 wxString file = dir + wxT("/mailcap");
1064 if ( wxFile::Exists(file) ) {
1065 ReadMailcap(file);
1066 }
1067
1068 file = dir + wxT("/mime.types");
1069 if ( wxFile::Exists(file) ) {
1070 ReadMimeTypes(file);
1071 }
1072 }
1073
1074 wxString strHome = wxGetenv(wxT("HOME"));
1075
1076 // and now the users mailcap
1077 wxString strUserMailcap = strHome + wxT("/.mailcap");
1078 if ( wxFile::Exists(strUserMailcap) ) {
1079 ReadMailcap(strUserMailcap);
1080 }
1081
1082 // read the users mime.types
1083 wxString strUserMimeTypes = strHome + wxT("/.mime.types");
1084 if ( wxFile::Exists(strUserMimeTypes) ) {
1085 ReadMimeTypes(strUserMimeTypes);
1086 }
1087
1088 // read KDE/GNOME tables
1089 ArrayIconHandlers& handlers = GetIconHandlers();
1090 size_t count = handlers.GetCount();
1091 for ( size_t hn = 0; hn < count; hn++ )
1092 handlers[hn]->GetMimeInfoRecords(this);
1093 }
1094
1095
1096 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1097 {
1098 size_t cnt = m_aEntries.GetCount();
1099 for (size_t i = 0; i < cnt; i++) delete m_aEntries[i];
1100 }
1101
1102
1103 wxFileType *
1104 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1105 {
1106 wxFileType *fileType = NULL;
1107 size_t count = m_aExtensions.GetCount();
1108 for ( size_t n = 0; n < count; n++ ) {
1109 wxString extensions = m_aExtensions[n];
1110 while ( !extensions.IsEmpty() ) {
1111 wxString field = extensions.BeforeFirst(wxT(' '));
1112 extensions = extensions.AfterFirst(wxT(' '));
1113
1114 // consider extensions as not being case-sensitive
1115 if ( field.IsSameAs(ext, FALSE /* no case */) ) {
1116 // found
1117 if (fileType == NULL) fileType = new wxFileType;
1118 fileType->m_impl->Init(this, n);
1119 // adds this mime type to _list_ of mime types with this extension
1120 }
1121 }
1122 }
1123
1124 return fileType;
1125 }
1126
1127 wxFileType *
1128 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1129 {
1130 // mime types are not case-sensitive
1131 wxString mimetype(mimeType);
1132 mimetype.MakeLower();
1133
1134 // first look for an exact match
1135 int index = m_aTypes.Index(mimetype);
1136 if ( index == wxNOT_FOUND ) {
1137 // then try to find "text/*" as match for "text/plain" (for example)
1138 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1139 // the whole string - ok.
1140 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
1141
1142 size_t nCount = m_aTypes.Count();
1143 for ( size_t n = 0; n < nCount; n++ ) {
1144 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1145 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") ) {
1146 index = n;
1147 break;
1148 }
1149 }
1150 }
1151
1152 if ( index != wxNOT_FOUND ) {
1153 wxFileType *fileType = new wxFileType;
1154 fileType->m_impl->Init(this, index);
1155
1156 return fileType;
1157 }
1158 else {
1159 // not found...
1160 return NULL;
1161 }
1162 }
1163
1164 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1165 {
1166 wxString extensions;
1167 const wxArrayString& exts = filetype.GetExtensions();
1168 size_t nExts = exts.GetCount();
1169 for ( size_t nExt = 0; nExt < nExts; nExt++ ) {
1170 if ( nExt > 0 ) {
1171 extensions += wxT(' ');
1172 }
1173 extensions += exts[nExt];
1174 }
1175
1176 AddMimeTypeInfo(filetype.GetMimeType(),
1177 extensions,
1178 filetype.GetDescription());
1179
1180 AddMailcapInfo(filetype.GetMimeType(),
1181 filetype.GetOpenCommand(),
1182 filetype.GetPrintCommand(),
1183 wxT(""),
1184 filetype.GetDescription());
1185 }
1186
1187 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
1188 const wxString& strExtensions,
1189 const wxString& strDesc)
1190 {
1191 int index = m_aTypes.Index(strMimeType);
1192 if ( index == wxNOT_FOUND ) {
1193 // add a new entry
1194 m_aTypes.Add(strMimeType);
1195 m_aEntries.Add(NULL);
1196 m_aExtensions.Add(strExtensions);
1197 m_aDescriptions.Add(strDesc);
1198 }
1199 else {
1200 // modify an existing one
1201 if ( !strDesc.IsEmpty() ) {
1202 m_aDescriptions[index] = strDesc; // replace old value
1203 }
1204 m_aExtensions[index] += ' ' + strExtensions;
1205 }
1206 }
1207
1208 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
1209 const wxString& strOpenCmd,
1210 const wxString& strPrintCmd,
1211 const wxString& strTest,
1212 const wxString& strDesc)
1213 {
1214 MailCapEntry *entry = new MailCapEntry(strOpenCmd, strPrintCmd, strTest);
1215
1216 int nIndex = m_aTypes.Index(strType);
1217 if ( nIndex == wxNOT_FOUND ) {
1218 // new file type
1219 m_aTypes.Add(strType);
1220
1221 m_aEntries.Add(entry);
1222 m_aExtensions.Add(wxT(""));
1223 m_aDescriptions.Add(strDesc);
1224 }
1225 else {
1226 // always append the entry in the tail of the list - info added with
1227 // this function can only come from AddFallbacks()
1228 MailCapEntry *entryOld = m_aEntries[nIndex];
1229 if ( entryOld )
1230 entry->Append(entryOld);
1231 else
1232 m_aEntries[nIndex] = entry;
1233 }
1234 }
1235
1236 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
1237 {
1238 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
1239 strFileName.c_str());
1240
1241 wxTextFile file(strFileName);
1242 if ( !file.Open() )
1243 return FALSE;
1244
1245 // the information we extract
1246 wxString strMimeType, strDesc, strExtensions;
1247
1248 size_t nLineCount = file.GetLineCount();
1249 const wxChar *pc = NULL;
1250 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1251 if ( pc == NULL ) {
1252 // now we're at the start of the line
1253 pc = file[nLine].c_str();
1254 }
1255 else {
1256 // we didn't finish with the previous line yet
1257 nLine--;
1258 }
1259
1260 // skip whitespace
1261 while ( wxIsspace(*pc) )
1262 pc++;
1263
1264 // comment or blank line?
1265 if ( *pc == wxT('#') || !*pc ) {
1266 // skip the whole line
1267 pc = NULL;
1268 continue;
1269 }
1270
1271 // detect file format
1272 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
1273 if ( pEqualSign == NULL ) {
1274 // brief format
1275 // ------------
1276
1277 // first field is mime type
1278 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ ) {
1279 strMimeType += *pc;
1280 }
1281
1282 // skip whitespace
1283 while ( wxIsspace(*pc) )
1284 pc++;
1285
1286 // take all the rest of the string
1287 strExtensions = pc;
1288
1289 // no description...
1290 strDesc.Empty();
1291 }
1292 else {
1293 // expanded format
1294 // ---------------
1295
1296 // the string on the left of '=' is the field name
1297 wxString strLHS(pc, pEqualSign - pc);
1298
1299 // eat whitespace
1300 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
1301 ;
1302
1303 const wxChar *pEnd;
1304 if ( *pc == wxT('"') ) {
1305 // the string is quoted and ends at the matching quote
1306 pEnd = wxStrchr(++pc, wxT('"'));
1307 if ( pEnd == NULL ) {
1308 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1309 "quoted string."),
1310 strFileName.c_str(), nLine + 1);
1311 }
1312 }
1313 else {
1314 // unquoted string ends at the first space or at the end of
1315 // line
1316 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
1317 ;
1318 }
1319
1320 // now we have the RHS (field value)
1321 wxString strRHS(pc, pEnd - pc);
1322
1323 // check what follows this entry
1324 if ( *pEnd == wxT('"') ) {
1325 // skip this quote
1326 pEnd++;
1327 }
1328
1329 for ( pc = pEnd; wxIsspace(*pc); pc++ )
1330 ;
1331
1332 // if there is something left, it may be either a '\\' to continue
1333 // the line or the next field of the same entry
1334 bool entryEnded = *pc == wxT('\0'),
1335 nextFieldOnSameLine = FALSE;
1336 if ( !entryEnded ) {
1337 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
1338 }
1339
1340 // now see what we got
1341 if ( strLHS == wxT("type") ) {
1342 strMimeType = strRHS;
1343 }
1344 else if ( strLHS == wxT("desc") ) {
1345 strDesc = strRHS;
1346 }
1347 else if ( strLHS == wxT("exts") ) {
1348 strExtensions = strRHS;
1349 }
1350 else {
1351 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1352 strFileName.c_str(), nLine + 1, strLHS.c_str());
1353 }
1354
1355 if ( !entryEnded ) {
1356 if ( !nextFieldOnSameLine )
1357 pc = NULL;
1358 //else: don't reset it
1359
1360 // as we don't reset strMimeType, the next field in this entry
1361 // will be interpreted correctly.
1362
1363 continue;
1364 }
1365 }
1366
1367 // although it doesn't seem to be covered by RFCs, some programs
1368 // (notably Netscape) create their entries with several comma
1369 // separated extensions (RFC mention the spaces only)
1370 strExtensions.Replace(wxT(","), wxT(" "));
1371
1372 // also deal with the leading dot
1373 if ( !strExtensions.IsEmpty() && strExtensions[0u] == wxT('.') )
1374 {
1375 strExtensions.erase(0, 1);
1376 }
1377
1378 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
1379
1380 // finished with this line
1381 pc = NULL;
1382 }
1383
1384 // check our data integriry
1385 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1386 m_aTypes.Count() == m_aExtensions.Count() &&
1387 m_aTypes.Count() == m_aDescriptions.Count() );
1388
1389 return TRUE;
1390 }
1391
1392 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
1393 bool fallback)
1394 {
1395 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
1396 strFileName.c_str());
1397
1398 wxTextFile file(strFileName);
1399 if ( !file.Open() )
1400 return FALSE;
1401
1402 // see the comments near the end of function for the reason we need these
1403 // variables (search for the next occurence of them)
1404 // indices of MIME types (in m_aTypes) we already found in this file
1405 wxArrayInt aEntryIndices;
1406 // aLastIndices[n] is the index of last element in
1407 // m_aEntries[aEntryIndices[n]] from this file
1408 wxArrayInt aLastIndices;
1409
1410 size_t nLineCount = file.GetLineCount();
1411 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1412 // now we're at the start of the line
1413 const wxChar *pc = file[nLine].c_str();
1414
1415 // skip whitespace
1416 while ( wxIsspace(*pc) )
1417 pc++;
1418
1419 // comment or empty string?
1420 if ( *pc == wxT('#') || *pc == wxT('\0') )
1421 continue;
1422
1423 // no, do parse
1424
1425 // what field are we currently in? The first 2 are fixed and there may
1426 // be an arbitrary number of other fields -- currently, we are not
1427 // interested in any of them, but we should parse them as well...
1428 enum
1429 {
1430 Field_Type,
1431 Field_OpenCmd,
1432 Field_Other
1433 } currentToken = Field_Type;
1434
1435 // the flags and field values on the current line
1436 bool needsterminal = FALSE,
1437 copiousoutput = FALSE;
1438 wxString strType,
1439 strOpenCmd,
1440 strPrintCmd,
1441 strTest,
1442 strDesc,
1443 curField; // accumulator
1444 for ( bool cont = TRUE; cont; pc++ ) {
1445 switch ( *pc ) {
1446 case wxT('\\'):
1447 // interpret the next character literally (notice that
1448 // backslash can be used for line continuation)
1449 if ( *++pc == wxT('\0') ) {
1450 // fetch the next line.
1451
1452 // pc currently points to nowhere, but after the next
1453 // pc++ in the for line it will point to the beginning
1454 // of the next line in the file
1455 pc = file[++nLine].c_str() - 1;
1456 }
1457 else {
1458 // just a normal character
1459 curField += *pc;
1460 }
1461 break;
1462
1463 case wxT('\0'):
1464 cont = FALSE; // end of line reached, exit the loop
1465
1466 // fall through
1467
1468 case wxT(';'):
1469 // store this field and start looking for the next one
1470
1471 // trim whitespaces from both sides
1472 curField.Trim(TRUE).Trim(FALSE);
1473
1474 switch ( currentToken ) {
1475 case Field_Type:
1476 strType = curField;
1477 if ( strType.Find(wxT('/')) == wxNOT_FOUND ) {
1478 // we interpret "type" as "type/*"
1479 strType += wxT("/*");
1480 }
1481
1482 currentToken = Field_OpenCmd;
1483 break;
1484
1485 case Field_OpenCmd:
1486 strOpenCmd = curField;
1487
1488 currentToken = Field_Other;
1489 break;
1490
1491 case Field_Other:
1492 {
1493 // "good" mailcap entry?
1494 bool ok = TRUE;
1495
1496 // is this something of the form foo=bar?
1497 const wxChar *pEq = wxStrchr(curField, wxT('='));
1498 if ( pEq != NULL ) {
1499 wxString lhs = curField.BeforeFirst(wxT('=')),
1500 rhs = curField.AfterFirst(wxT('='));
1501
1502 lhs.Trim(TRUE); // from right
1503 rhs.Trim(FALSE); // from left
1504
1505 if ( lhs == wxT("print") )
1506 strPrintCmd = rhs;
1507 else if ( lhs == wxT("test") )
1508 strTest = rhs;
1509 else if ( lhs == wxT("description") ) {
1510 // it might be quoted
1511 if ( rhs[0u] == wxT('"') &&
1512 rhs.Last() == wxT('"') ) {
1513 strDesc = wxString(rhs.c_str() + 1,
1514 rhs.Len() - 2);
1515 }
1516 else {
1517 strDesc = rhs;
1518 }
1519 }
1520 else if ( lhs == wxT("compose") ||
1521 lhs == wxT("composetyped") ||
1522 lhs == wxT("notes") ||
1523 lhs == wxT("edit") )
1524 ; // ignore
1525 else
1526 ok = FALSE;
1527
1528 }
1529 else {
1530 // no, it's a simple flag
1531 // TODO support the flags:
1532 // 1. create an xterm for 'needsterminal'
1533 // 2. append "| $PAGER" for 'copiousoutput'
1534 if ( curField == wxT("needsterminal") )
1535 needsterminal = TRUE;
1536 else if ( curField == wxT("copiousoutput") )
1537 copiousoutput = TRUE;
1538 else if ( curField == wxT("textualnewlines") )
1539 ; // ignore
1540 else
1541 ok = FALSE;
1542 }
1543
1544 if ( !ok )
1545 {
1546 if ( !IsKnownUnimportantField(curField) )
1547 {
1548 // don't flood the user with error
1549 // messages if we don't understand
1550 // something in his mailcap, but give
1551 // them in debug mode because this might
1552 // be useful for the programmer
1553 wxLogDebug
1554 (
1555 wxT("Mailcap file %s, line %d: "
1556 "unknown field '%s' for the "
1557 "MIME type '%s' ignored."),
1558 strFileName.c_str(),
1559 nLine + 1,
1560 curField.c_str(),
1561 strType.c_str()
1562 );
1563 }
1564 }
1565 }
1566
1567 // it already has this value
1568 //currentToken = Field_Other;
1569 break;
1570
1571 default:
1572 wxFAIL_MSG(wxT("unknown field type in mailcap"));
1573 }
1574
1575 // next token starts immediately after ';'
1576 curField.Empty();
1577 break;
1578
1579 default:
1580 curField += *pc;
1581 }
1582 }
1583
1584 // check that we really read something reasonable
1585 if ( currentToken == Field_Type || currentToken == Field_OpenCmd ) {
1586 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1587 "ignored."),
1588 strFileName.c_str(), nLine + 1);
1589 }
1590 else {
1591 MailCapEntry *entry = new MailCapEntry(strOpenCmd,
1592 strPrintCmd,
1593 strTest);
1594
1595 // NB: because of complications below (we must get entries priority
1596 // right), we can't use AddMailcapInfo() here, unfortunately.
1597 strType.MakeLower();
1598 int nIndex = m_aTypes.Index(strType);
1599 if ( nIndex == wxNOT_FOUND ) {
1600 // new file type
1601 m_aTypes.Add(strType);
1602
1603 m_aEntries.Add(entry);
1604 m_aExtensions.Add(wxT(""));
1605 m_aDescriptions.Add(strDesc);
1606 }
1607 else {
1608 // modify the existing entry: the entries in one and the same
1609 // file are read in top-to-bottom order, i.e. the entries read
1610 // first should be tried before the entries below. However,
1611 // the files read later should override the settings in the
1612 // files read before (except if fallback is TRUE), thus we
1613 // Insert() the new entry to the list if it has already
1614 // occured in _this_ file, but Prepend() it if it occured in
1615 // some of the previous ones and Append() to it in the
1616 // fallback case
1617
1618 if ( fallback ) {
1619 // 'fallback' parameter prevents the entries from this
1620 // file from overriding the other ones - always append
1621 MailCapEntry *entryOld = m_aEntries[nIndex];
1622 if ( entryOld )
1623 entry->Append(entryOld);
1624 else
1625 m_aEntries[nIndex] = entry;
1626 }
1627 else {
1628 int entryIndex = aEntryIndices.Index(nIndex);
1629 if ( entryIndex == wxNOT_FOUND ) {
1630 // first time in this file
1631 aEntryIndices.Add(nIndex);
1632 aLastIndices.Add(0);
1633
1634 entry->Prepend(m_aEntries[nIndex]);
1635 m_aEntries[nIndex] = entry;
1636 }
1637 else {
1638 // not the first time in _this_ file
1639 size_t nEntryIndex = (size_t)entryIndex;
1640 MailCapEntry *entryOld = m_aEntries[nIndex];
1641 if ( entryOld )
1642 entry->Insert(entryOld, aLastIndices[nEntryIndex]);
1643 else
1644 m_aEntries[nIndex] = entry;
1645
1646 // the indices were shifted by 1
1647 aLastIndices[nEntryIndex]++;
1648 }
1649 }
1650
1651 if ( !strDesc.IsEmpty() ) {
1652 // replace the old one - what else can we do??
1653 m_aDescriptions[nIndex] = strDesc;
1654 }
1655 }
1656 }
1657
1658 // check our data integriry
1659 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1660 m_aTypes.Count() == m_aExtensions.Count() &&
1661 m_aTypes.Count() == m_aDescriptions.Count() );
1662 }
1663
1664 return TRUE;
1665 }
1666
1667 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1668 {
1669 mimetypes.Empty();
1670
1671 wxString type;
1672 size_t count = m_aTypes.GetCount();
1673 for ( size_t n = 0; n < count; n++ )
1674 {
1675 // don't return template types from here (i.e. anything containg '*')
1676 type = m_aTypes[n];
1677 if ( type.Find(_T('*')) == wxNOT_FOUND )
1678 {
1679 mimetypes.Add(type);
1680 }
1681 }
1682
1683 return mimetypes.GetCount();
1684 }
1685
1686 // ----------------------------------------------------------------------------
1687 // private functions
1688 // ----------------------------------------------------------------------------
1689
1690 static bool IsKnownUnimportantField(const wxString& fieldAll)
1691 {
1692 static const wxChar *knownFields[] =
1693 {
1694 _T("x-mozilla-flags"),
1695 _T("nametemplate"),
1696 _T("textualnewlines"),
1697 };
1698
1699 wxString field = fieldAll.BeforeFirst(_T('='));
1700 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
1701 {
1702 if ( field.CmpNoCase(knownFields[n]) == 0 )
1703 return TRUE;
1704 }
1705
1706 return FALSE;
1707 }
1708
1709 #endif
1710 // wxUSE_FILE && wxUSE_TEXTFILE
1711