]> git.saurik.com Git - wxWidgets.git/blob - src/unix/mimetype.cpp
test cvs commit
[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 // notice that an empty command would always succeed (it's ok)
914 command = wxFileType::ExpandCommand(entry->GetTestCmd(), params);
915
916 // suppress the command output
917 if ( !command.IsEmpty() )
918 {
919 if ( wxSystem(command) == 0 ) {
920 // ok, passed
921 wxLogTrace(TRACE_MIME,
922 wxT("Test '%s' for mime type '%s' succeeded."),
923 command.c_str(), params.GetMimeType().c_str());
924 break;
925 }
926 else {
927 wxLogTrace(TRACE_MIME,
928 wxT("Test '%s' for mime type '%s' failed."),
929 command.c_str(), params.GetMimeType().c_str());
930 }
931 }
932
933 entry = entry->GetNext();
934 }
935
936 return entry;
937 }
938
939 bool wxFileTypeImpl::GetIcon(wxIcon *icon) const
940 {
941 wxArrayString mimetypes;
942 GetMimeTypes(mimetypes);
943
944 ArrayIconHandlers& handlers = m_manager->GetIconHandlers();
945 size_t count = handlers.GetCount();
946 size_t counttypes = mimetypes.GetCount();
947 for ( size_t n = 0; n < count; n++ )
948 {
949 for ( size_t n2 = 0; n2 < counttypes; n2++ )
950 {
951 if ( handlers[n]->GetIcon(mimetypes[n2], icon) )
952 return TRUE;
953 }
954 }
955
956 return FALSE;
957 }
958
959
960 bool
961 wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
962 {
963 mimeTypes.Clear();
964 for (size_t i = 0; i < m_index.GetCount(); i++)
965 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
966 return TRUE;
967 }
968
969
970 bool
971 wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd,
972 const wxFileType::MessageParameters& params,
973 bool open) const
974 {
975 MailCapEntry *entry = GetEntry(params);
976 if ( entry == NULL ) {
977 // all tests failed...
978 return FALSE;
979 }
980
981 wxString cmd = open ? entry->GetOpenCmd() : entry->GetPrintCmd();
982 if ( cmd.IsEmpty() ) {
983 // may happen, especially for "print"
984 return FALSE;
985 }
986
987 *expandedCmd = wxFileType::ExpandCommand(cmd, params);
988 return TRUE;
989 }
990
991 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
992 {
993 wxString strExtensions = m_manager->GetExtension(m_index[0]);
994 extensions.Empty();
995
996 // one extension in the space or comma delimitid list
997 wxString strExt;
998 for ( const wxChar *p = strExtensions; ; p++ ) {
999 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') ) {
1000 if ( !strExt.IsEmpty() ) {
1001 extensions.Add(strExt);
1002 strExt.Empty();
1003 }
1004 //else: repeated spaces (shouldn't happen, but it's not that
1005 // important if it does happen)
1006
1007 if ( *p == wxT('\0') )
1008 break;
1009 }
1010 else if ( *p == wxT('.') ) {
1011 // remove the dot from extension (but only if it's the first char)
1012 if ( !strExt.IsEmpty() ) {
1013 strExt += wxT('.');
1014 }
1015 //else: no, don't append it
1016 }
1017 else {
1018 strExt += *p;
1019 }
1020 }
1021
1022 return TRUE;
1023 }
1024
1025 // ----------------------------------------------------------------------------
1026 // wxMimeTypesManagerImpl (Unix)
1027 // ----------------------------------------------------------------------------
1028
1029 /* static */
1030 ArrayIconHandlers& wxMimeTypesManagerImpl::GetIconHandlers()
1031 {
1032 if ( ms_iconHandlers.GetCount() == 0 )
1033 {
1034 ms_iconHandlers.Add(&gs_iconHandlerGNOME);
1035 ms_iconHandlers.Add(&gs_iconHandlerKDE);
1036 }
1037
1038 return ms_iconHandlers;
1039 }
1040
1041 // read system and user mailcaps (TODO implement mime.types support)
1042 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1043 {
1044 // directories where we look for mailcap and mime.types by default
1045 // (taken from metamail(1) sources)
1046 static const wxChar *aStandardLocations[] =
1047 {
1048 wxT("/etc"),
1049 wxT("/usr/etc"),
1050 wxT("/usr/local/etc"),
1051 wxT("/etc/mail"),
1052 wxT("/usr/public/lib")
1053 };
1054
1055 // first read the system wide file(s)
1056 size_t n;
1057 for ( n = 0; n < WXSIZEOF(aStandardLocations); n++ ) {
1058 wxString dir = aStandardLocations[n];
1059
1060 wxString file = dir + wxT("/mailcap");
1061 if ( wxFile::Exists(file) ) {
1062 ReadMailcap(file);
1063 }
1064
1065 file = dir + wxT("/mime.types");
1066 if ( wxFile::Exists(file) ) {
1067 ReadMimeTypes(file);
1068 }
1069 }
1070
1071 wxString strHome = wxGetenv(wxT("HOME"));
1072
1073 // and now the users mailcap
1074 wxString strUserMailcap = strHome + wxT("/.mailcap");
1075 if ( wxFile::Exists(strUserMailcap) ) {
1076 ReadMailcap(strUserMailcap);
1077 }
1078
1079 // read the users mime.types
1080 wxString strUserMimeTypes = strHome + wxT("/.mime.types");
1081 if ( wxFile::Exists(strUserMimeTypes) ) {
1082 ReadMimeTypes(strUserMimeTypes);
1083 }
1084
1085 // read KDE/GNOME tables
1086 ArrayIconHandlers& handlers = GetIconHandlers();
1087 size_t count = handlers.GetCount();
1088 for ( size_t hn = 0; hn < count; hn++ )
1089 handlers[hn]->GetMimeInfoRecords(this);
1090 }
1091
1092
1093 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1094 {
1095 size_t cnt = m_aEntries.GetCount();
1096 for (size_t i = 0; i < cnt; i++) delete m_aEntries[i];
1097 }
1098
1099
1100 wxFileType *
1101 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1102 {
1103 wxFileType *fileType = NULL;
1104 size_t count = m_aExtensions.GetCount();
1105 for ( size_t n = 0; n < count; n++ ) {
1106 wxString extensions = m_aExtensions[n];
1107 while ( !extensions.IsEmpty() ) {
1108 wxString field = extensions.BeforeFirst(wxT(' '));
1109 extensions = extensions.AfterFirst(wxT(' '));
1110
1111 // consider extensions as not being case-sensitive
1112 if ( field.IsSameAs(ext, FALSE /* no case */) ) {
1113 // found
1114 if (fileType == NULL) fileType = new wxFileType;
1115 fileType->m_impl->Init(this, n);
1116 // adds this mime type to _list_ of mime types with this extension
1117 }
1118 }
1119 }
1120
1121 return fileType;
1122 }
1123
1124 wxFileType *
1125 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1126 {
1127 // mime types are not case-sensitive
1128 wxString mimetype(mimeType);
1129 mimetype.MakeLower();
1130
1131 // first look for an exact match
1132 int index = m_aTypes.Index(mimetype);
1133 if ( index == wxNOT_FOUND ) {
1134 // then try to find "text/*" as match for "text/plain" (for example)
1135 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1136 // the whole string - ok.
1137 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
1138
1139 size_t nCount = m_aTypes.Count();
1140 for ( size_t n = 0; n < nCount; n++ ) {
1141 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1142 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") ) {
1143 index = n;
1144 break;
1145 }
1146 }
1147 }
1148
1149 if ( index != wxNOT_FOUND ) {
1150 wxFileType *fileType = new wxFileType;
1151 fileType->m_impl->Init(this, index);
1152
1153 return fileType;
1154 }
1155 else {
1156 // not found...
1157 return NULL;
1158 }
1159 }
1160
1161 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1162 {
1163 wxString extensions;
1164 const wxArrayString& exts = filetype.GetExtensions();
1165 size_t nExts = exts.GetCount();
1166 for ( size_t nExt = 0; nExt < nExts; nExt++ ) {
1167 if ( nExt > 0 ) {
1168 extensions += wxT(' ');
1169 }
1170 extensions += exts[nExt];
1171 }
1172
1173 AddMimeTypeInfo(filetype.GetMimeType(),
1174 extensions,
1175 filetype.GetDescription());
1176
1177 AddMailcapInfo(filetype.GetMimeType(),
1178 filetype.GetOpenCommand(),
1179 filetype.GetPrintCommand(),
1180 wxT(""),
1181 filetype.GetDescription());
1182 }
1183
1184 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
1185 const wxString& strExtensions,
1186 const wxString& strDesc)
1187 {
1188 int index = m_aTypes.Index(strMimeType);
1189 if ( index == wxNOT_FOUND ) {
1190 // add a new entry
1191 m_aTypes.Add(strMimeType);
1192 m_aEntries.Add(NULL);
1193 m_aExtensions.Add(strExtensions);
1194 m_aDescriptions.Add(strDesc);
1195 }
1196 else {
1197 // modify an existing one
1198 if ( !strDesc.IsEmpty() ) {
1199 m_aDescriptions[index] = strDesc; // replace old value
1200 }
1201 m_aExtensions[index] += ' ' + strExtensions;
1202 }
1203 }
1204
1205 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
1206 const wxString& strOpenCmd,
1207 const wxString& strPrintCmd,
1208 const wxString& strTest,
1209 const wxString& strDesc)
1210 {
1211 MailCapEntry *entry = new MailCapEntry(strOpenCmd, strPrintCmd, strTest);
1212
1213 int nIndex = m_aTypes.Index(strType);
1214 if ( nIndex == wxNOT_FOUND ) {
1215 // new file type
1216 m_aTypes.Add(strType);
1217
1218 m_aEntries.Add(entry);
1219 m_aExtensions.Add(wxT(""));
1220 m_aDescriptions.Add(strDesc);
1221 }
1222 else {
1223 // always append the entry in the tail of the list - info added with
1224 // this function can only come from AddFallbacks()
1225 MailCapEntry *entryOld = m_aEntries[nIndex];
1226 if ( entryOld )
1227 entry->Append(entryOld);
1228 else
1229 m_aEntries[nIndex] = entry;
1230 }
1231 }
1232
1233 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
1234 {
1235 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
1236 strFileName.c_str());
1237
1238 wxTextFile file(strFileName);
1239 if ( !file.Open() )
1240 return FALSE;
1241
1242 // the information we extract
1243 wxString strMimeType, strDesc, strExtensions;
1244
1245 size_t nLineCount = file.GetLineCount();
1246 const wxChar *pc = NULL;
1247 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1248 if ( pc == NULL ) {
1249 // now we're at the start of the line
1250 pc = file[nLine].c_str();
1251 }
1252 else {
1253 // we didn't finish with the previous line yet
1254 nLine--;
1255 }
1256
1257 // skip whitespace
1258 while ( wxIsspace(*pc) )
1259 pc++;
1260
1261 // comment or blank line?
1262 if ( *pc == wxT('#') || !*pc ) {
1263 // skip the whole line
1264 pc = NULL;
1265 continue;
1266 }
1267
1268 // detect file format
1269 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
1270 if ( pEqualSign == NULL ) {
1271 // brief format
1272 // ------------
1273
1274 // first field is mime type
1275 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ ) {
1276 strMimeType += *pc;
1277 }
1278
1279 // skip whitespace
1280 while ( wxIsspace(*pc) )
1281 pc++;
1282
1283 // take all the rest of the string
1284 strExtensions = pc;
1285
1286 // no description...
1287 strDesc.Empty();
1288 }
1289 else {
1290 // expanded format
1291 // ---------------
1292
1293 // the string on the left of '=' is the field name
1294 wxString strLHS(pc, pEqualSign - pc);
1295
1296 // eat whitespace
1297 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
1298 ;
1299
1300 const wxChar *pEnd;
1301 if ( *pc == wxT('"') ) {
1302 // the string is quoted and ends at the matching quote
1303 pEnd = wxStrchr(++pc, wxT('"'));
1304 if ( pEnd == NULL ) {
1305 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1306 "quoted string."),
1307 strFileName.c_str(), nLine + 1);
1308 }
1309 }
1310 else {
1311 // unquoted string ends at the first space or at the end of
1312 // line
1313 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
1314 ;
1315 }
1316
1317 // now we have the RHS (field value)
1318 wxString strRHS(pc, pEnd - pc);
1319
1320 // check what follows this entry
1321 if ( *pEnd == wxT('"') ) {
1322 // skip this quote
1323 pEnd++;
1324 }
1325
1326 for ( pc = pEnd; wxIsspace(*pc); pc++ )
1327 ;
1328
1329 // if there is something left, it may be either a '\\' to continue
1330 // the line or the next field of the same entry
1331 bool entryEnded = *pc == wxT('\0'),
1332 nextFieldOnSameLine = FALSE;
1333 if ( !entryEnded ) {
1334 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
1335 }
1336
1337 // now see what we got
1338 if ( strLHS == wxT("type") ) {
1339 strMimeType = strRHS;
1340 }
1341 else if ( strLHS == wxT("desc") ) {
1342 strDesc = strRHS;
1343 }
1344 else if ( strLHS == wxT("exts") ) {
1345 strExtensions = strRHS;
1346 }
1347 else {
1348 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1349 strFileName.c_str(), nLine + 1, strLHS.c_str());
1350 }
1351
1352 if ( !entryEnded ) {
1353 if ( !nextFieldOnSameLine )
1354 pc = NULL;
1355 //else: don't reset it
1356
1357 // as we don't reset strMimeType, the next field in this entry
1358 // will be interpreted correctly.
1359
1360 continue;
1361 }
1362 }
1363
1364 // although it doesn't seem to be covered by RFCs, some programs
1365 // (notably Netscape) create their entries with several comma
1366 // separated extensions (RFC mention the spaces only)
1367 strExtensions.Replace(wxT(","), wxT(" "));
1368
1369 // also deal with the leading dot
1370 if ( !strExtensions.IsEmpty() && strExtensions[0u] == wxT('.') )
1371 {
1372 strExtensions.erase(0, 1);
1373 }
1374
1375 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
1376
1377 // finished with this line
1378 pc = NULL;
1379 }
1380
1381 // check our data integriry
1382 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1383 m_aTypes.Count() == m_aExtensions.Count() &&
1384 m_aTypes.Count() == m_aDescriptions.Count() );
1385
1386 return TRUE;
1387 }
1388
1389 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
1390 bool fallback)
1391 {
1392 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
1393 strFileName.c_str());
1394
1395 wxTextFile file(strFileName);
1396 if ( !file.Open() )
1397 return FALSE;
1398
1399 // see the comments near the end of function for the reason we need these
1400 // variables (search for the next occurence of them)
1401 // indices of MIME types (in m_aTypes) we already found in this file
1402 wxArrayInt aEntryIndices;
1403 // aLastIndices[n] is the index of last element in
1404 // m_aEntries[aEntryIndices[n]] from this file
1405 wxArrayInt aLastIndices;
1406
1407 size_t nLineCount = file.GetLineCount();
1408 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1409 // now we're at the start of the line
1410 const wxChar *pc = file[nLine].c_str();
1411
1412 // skip whitespace
1413 while ( wxIsspace(*pc) )
1414 pc++;
1415
1416 // comment or empty string?
1417 if ( *pc == wxT('#') || *pc == wxT('\0') )
1418 continue;
1419
1420 // no, do parse
1421
1422 // what field are we currently in? The first 2 are fixed and there may
1423 // be an arbitrary number of other fields -- currently, we are not
1424 // interested in any of them, but we should parse them as well...
1425 enum
1426 {
1427 Field_Type,
1428 Field_OpenCmd,
1429 Field_Other
1430 } currentToken = Field_Type;
1431
1432 // the flags and field values on the current line
1433 bool needsterminal = FALSE,
1434 copiousoutput = FALSE;
1435 wxString strType,
1436 strOpenCmd,
1437 strPrintCmd,
1438 strTest,
1439 strDesc,
1440 curField; // accumulator
1441 for ( bool cont = TRUE; cont; pc++ ) {
1442 switch ( *pc ) {
1443 case wxT('\\'):
1444 // interpret the next character literally (notice that
1445 // backslash can be used for line continuation)
1446 if ( *++pc == wxT('\0') ) {
1447 // fetch the next line.
1448
1449 // pc currently points to nowhere, but after the next
1450 // pc++ in the for line it will point to the beginning
1451 // of the next line in the file
1452 pc = file[++nLine].c_str() - 1;
1453 }
1454 else {
1455 // just a normal character
1456 curField += *pc;
1457 }
1458 break;
1459
1460 case wxT('\0'):
1461 cont = FALSE; // end of line reached, exit the loop
1462
1463 // fall through
1464
1465 case wxT(';'):
1466 // store this field and start looking for the next one
1467
1468 // trim whitespaces from both sides
1469 curField.Trim(TRUE).Trim(FALSE);
1470
1471 switch ( currentToken ) {
1472 case Field_Type:
1473 strType = curField;
1474 if ( strType.Find(wxT('/')) == wxNOT_FOUND ) {
1475 // we interpret "type" as "type/*"
1476 strType += wxT("/*");
1477 }
1478
1479 currentToken = Field_OpenCmd;
1480 break;
1481
1482 case Field_OpenCmd:
1483 strOpenCmd = curField;
1484
1485 currentToken = Field_Other;
1486 break;
1487
1488 case Field_Other:
1489 {
1490 // "good" mailcap entry?
1491 bool ok = TRUE;
1492
1493 // is this something of the form foo=bar?
1494 const wxChar *pEq = wxStrchr(curField, wxT('='));
1495 if ( pEq != NULL ) {
1496 wxString lhs = curField.BeforeFirst(wxT('=')),
1497 rhs = curField.AfterFirst(wxT('='));
1498
1499 lhs.Trim(TRUE); // from right
1500 rhs.Trim(FALSE); // from left
1501
1502 if ( lhs == wxT("print") )
1503 strPrintCmd = rhs;
1504 else if ( lhs == wxT("test") )
1505 strTest = rhs;
1506 else if ( lhs == wxT("description") ) {
1507 // it might be quoted
1508 if ( rhs[0u] == wxT('"') &&
1509 rhs.Last() == wxT('"') ) {
1510 strDesc = wxString(rhs.c_str() + 1,
1511 rhs.Len() - 2);
1512 }
1513 else {
1514 strDesc = rhs;
1515 }
1516 }
1517 else if ( lhs == wxT("compose") ||
1518 lhs == wxT("composetyped") ||
1519 lhs == wxT("notes") ||
1520 lhs == wxT("edit") )
1521 ; // ignore
1522 else
1523 ok = FALSE;
1524
1525 }
1526 else {
1527 // no, it's a simple flag
1528 // TODO support the flags:
1529 // 1. create an xterm for 'needsterminal'
1530 // 2. append "| $PAGER" for 'copiousoutput'
1531 if ( curField == wxT("needsterminal") )
1532 needsterminal = TRUE;
1533 else if ( curField == wxT("copiousoutput") )
1534 copiousoutput = TRUE;
1535 else if ( curField == wxT("textualnewlines") )
1536 ; // ignore
1537 else
1538 ok = FALSE;
1539 }
1540
1541 if ( !ok )
1542 {
1543 if ( !IsKnownUnimportantField(curField) )
1544 {
1545 // don't flood the user with error
1546 // messages if we don't understand
1547 // something in his mailcap, but give
1548 // them in debug mode because this might
1549 // be useful for the programmer
1550 wxLogDebug
1551 (
1552 wxT("Mailcap file %s, line %d: "
1553 "unknown field '%s' for the "
1554 "MIME type '%s' ignored."),
1555 strFileName.c_str(),
1556 nLine + 1,
1557 curField.c_str(),
1558 strType.c_str()
1559 );
1560 }
1561 }
1562 }
1563
1564 // it already has this value
1565 //currentToken = Field_Other;
1566 break;
1567
1568 default:
1569 wxFAIL_MSG(wxT("unknown field type in mailcap"));
1570 }
1571
1572 // next token starts immediately after ';'
1573 curField.Empty();
1574 break;
1575
1576 default:
1577 curField += *pc;
1578 }
1579 }
1580
1581 // check that we really read something reasonable
1582 if ( currentToken == Field_Type || currentToken == Field_OpenCmd ) {
1583 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1584 "ignored."),
1585 strFileName.c_str(), nLine + 1);
1586 }
1587 else {
1588 MailCapEntry *entry = new MailCapEntry(strOpenCmd,
1589 strPrintCmd,
1590 strTest);
1591
1592 // NB: because of complications below (we must get entries priority
1593 // right), we can't use AddMailcapInfo() here, unfortunately.
1594 strType.MakeLower();
1595 int nIndex = m_aTypes.Index(strType);
1596 if ( nIndex == wxNOT_FOUND ) {
1597 // new file type
1598 m_aTypes.Add(strType);
1599
1600 m_aEntries.Add(entry);
1601 m_aExtensions.Add(wxT(""));
1602 m_aDescriptions.Add(strDesc);
1603 }
1604 else {
1605 // modify the existing entry: the entries in one and the same
1606 // file are read in top-to-bottom order, i.e. the entries read
1607 // first should be tried before the entries below. However,
1608 // the files read later should override the settings in the
1609 // files read before (except if fallback is TRUE), thus we
1610 // Insert() the new entry to the list if it has already
1611 // occured in _this_ file, but Prepend() it if it occured in
1612 // some of the previous ones and Append() to it in the
1613 // fallback case
1614
1615 if ( fallback ) {
1616 // 'fallback' parameter prevents the entries from this
1617 // file from overriding the other ones - always append
1618 MailCapEntry *entryOld = m_aEntries[nIndex];
1619 if ( entryOld )
1620 entry->Append(entryOld);
1621 else
1622 m_aEntries[nIndex] = entry;
1623 }
1624 else {
1625 int entryIndex = aEntryIndices.Index(nIndex);
1626 if ( entryIndex == wxNOT_FOUND ) {
1627 // first time in this file
1628 aEntryIndices.Add(nIndex);
1629 aLastIndices.Add(0);
1630
1631 entry->Prepend(m_aEntries[nIndex]);
1632 m_aEntries[nIndex] = entry;
1633 }
1634 else {
1635 // not the first time in _this_ file
1636 size_t nEntryIndex = (size_t)entryIndex;
1637 MailCapEntry *entryOld = m_aEntries[nIndex];
1638 if ( entryOld )
1639 entry->Insert(entryOld, aLastIndices[nEntryIndex]);
1640 else
1641 m_aEntries[nIndex] = entry;
1642
1643 // the indices were shifted by 1
1644 aLastIndices[nEntryIndex]++;
1645 }
1646 }
1647
1648 if ( !strDesc.IsEmpty() ) {
1649 // replace the old one - what else can we do??
1650 m_aDescriptions[nIndex] = strDesc;
1651 }
1652 }
1653 }
1654
1655 // check our data integriry
1656 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1657 m_aTypes.Count() == m_aExtensions.Count() &&
1658 m_aTypes.Count() == m_aDescriptions.Count() );
1659 }
1660
1661 return TRUE;
1662 }
1663
1664 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1665 {
1666 mimetypes.Empty();
1667
1668 wxString type;
1669 size_t count = m_aTypes.GetCount();
1670 for ( size_t n = 0; n < count; n++ )
1671 {
1672 // don't return template types from here (i.e. anything containg '*')
1673 type = m_aTypes[n];
1674 if ( type.Find(_T('*')) == wxNOT_FOUND )
1675 {
1676 mimetypes.Add(type);
1677 }
1678 }
1679
1680 return mimetypes.GetCount();
1681 }
1682
1683 // ----------------------------------------------------------------------------
1684 // private functions
1685 // ----------------------------------------------------------------------------
1686
1687 static bool IsKnownUnimportantField(const wxString& fieldAll)
1688 {
1689 static const wxChar *knownFields[] =
1690 {
1691 _T("x-mozilla-flags"),
1692 _T("nametemplate"),
1693 _T("textualnewlines"),
1694 };
1695
1696 wxString field = fieldAll.BeforeFirst(_T('='));
1697 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
1698 {
1699 if ( field.CmpNoCase(knownFields[n]) == 0 )
1700 return TRUE;
1701 }
1702
1703 return FALSE;
1704 }
1705
1706 #endif
1707 // wxUSE_FILE && wxUSE_TEXTFILE
1708