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