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