fix memory leak (coverity checker CID 53)
[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 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc);
651 }
652
653 void wxMimeTypesManagerImpl::LoadGnomeMimeTypesFromMimeFile(const wxString& filename)
654 {
655 wxTextFile textfile(filename);
656 if ( !textfile.Open() )
657 return;
658
659 wxLogTrace(TRACE_MIME,
660 wxT("--- Opened Gnome file %s ---"),
661 filename.c_str());
662
663 // values for the entry being parsed
664 wxString curMimeType, curExtList;
665
666 const wxChar *pc;
667 size_t nLineCount = textfile.GetLineCount();
668 for ( size_t nLine = 0; /* nothing */; nLine++ )
669 {
670 if ( nLine < nLineCount )
671 {
672 pc = textfile[nLine].c_str();
673 if ( *pc == wxT('#') )
674 {
675 // skip comments
676 continue;
677 }
678 }
679 else
680 {
681 // so that we will fall into the "if" below
682 pc = NULL;
683 }
684
685 if ( !pc || !*pc )
686 {
687 // end of the entry
688 if ( !curMimeType.empty() && !curExtList.empty() )
689 {
690 wxLogTrace(TRACE_MIME,
691 wxT("--- At end of Gnome file finding mimetype %s ---"),
692 curMimeType.c_str());
693
694 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
695 }
696
697 if ( !pc )
698 {
699 // the end: this can only happen if nLine == nLineCount
700 break;
701 }
702
703 curExtList.Empty();
704
705 continue;
706 }
707
708 // what do we have here?
709 if ( *pc == wxT('\t') )
710 {
711 // this is a field=value ling
712 pc++; // skip leading TAB
713
714 static const int lenField = 5; // strlen("ext: ")
715 if ( wxStrncmp(pc, wxT("ext: "), lenField) == 0 )
716 {
717 // skip it and take everything left until the end of line
718 curExtList = pc + lenField;
719 }
720 //else: some other field, we don't care
721 }
722 else
723 {
724 // this is the start of the new section
725 wxLogTrace(TRACE_MIME,
726 wxT("--- In Gnome file finding mimetype %s ---"),
727 curMimeType.c_str());
728
729 if (! curMimeType.empty())
730 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
731
732 curMimeType.Empty();
733
734 while ( *pc != wxT(':') && *pc != wxT('\0') )
735 {
736 curMimeType += *pc++;
737 }
738 }
739 }
740 }
741
742
743 void wxMimeTypesManagerImpl::LoadGnomeMimeFilesFromDir(
744 const wxString& dirbase, const wxArrayString& dirs)
745 {
746 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
747 wxT("base directory shouldn't end with a slash") );
748
749 wxString dirname = dirbase;
750 dirname << wxT("/mime-info");
751
752 if ( !wxDir::Exists(dirname) )
753 return;
754
755 wxDir dir(dirname);
756 if ( !dir.IsOpened() )
757 return;
758
759 // we will concatenate it with filename to get the full path below
760 dirname += wxT('/');
761
762 wxString filename;
763 bool cont;
764
765 cont = dir.GetFirst(&filename, wxT("*.mime"), wxDIR_FILES);
766 while ( cont )
767 {
768 LoadGnomeMimeTypesFromMimeFile(dirname + filename);
769
770 cont = dir.GetNext(&filename);
771 }
772
773 cont = dir.GetFirst(&filename, wxT("*.keys"), wxDIR_FILES);
774 while ( cont )
775 {
776 LoadGnomeDataFromKeyFile(dirname + filename, dirs);
777
778 cont = dir.GetNext(&filename);
779 }
780
781 // FIXME: Hack alert: We scan all icons and deduce the
782 // mime-type from the file name.
783 dirname = dirbase;
784 dirname << wxT("/pixmaps/document-icons");
785
786 // these are always empty in this file
787 wxArrayString strExtensions;
788 wxString strDesc;
789
790 if ( !wxDir::Exists(dirname) )
791 {
792 // Just test for default GPE dir also
793 dirname = wxT("/usr/share/gpe/pixmaps/default/filemanager/document-icons");
794
795 if ( !wxDir::Exists(dirname) )
796 return;
797 }
798
799 wxDir dir2( dirname );
800
801 cont = dir2.GetFirst(&filename, wxT("gnome-*.png"), wxDIR_FILES);
802 while ( cont )
803 {
804 wxString mimeType = filename;
805 mimeType.Remove( 0, 6 ); // remove "gnome-"
806 mimeType.Remove( mimeType.Len() - 4, 4 ); // remove ".png"
807 int pos = mimeType.Find( wxT("-") );
808 if (pos != wxNOT_FOUND)
809 {
810 mimeType.SetChar( pos, wxT('/') );
811 wxString iconFile = dirname;
812 iconFile << wxT("/");
813 iconFile << filename;
814 AddToMimeData( mimeType, iconFile, NULL, strExtensions, strDesc, true );
815 }
816
817 cont = dir2.GetNext(&filename);
818 }
819 }
820
821 void wxMimeTypesManagerImpl::GetGnomeMimeInfo(const wxString& sExtraDir)
822 {
823 wxArrayString dirs;
824
825 wxString gnomedir = wxGetenv( wxT("GNOMEDIR") );
826 if (!gnomedir.empty())
827 {
828 gnomedir << wxT("/share");
829 dirs.Add( gnomedir );
830 }
831
832 dirs.Add(wxT("/usr/share"));
833 dirs.Add(wxT("/usr/local/share"));
834
835 gnomedir = wxGetHomeDir();
836 gnomedir << wxT("/.gnome");
837 dirs.Add( gnomedir );
838
839 if (!sExtraDir.empty())
840 dirs.Add( sExtraDir );
841
842 size_t nDirs = dirs.GetCount();
843 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
844 {
845 LoadGnomeMimeFilesFromDir(dirs[nDir], dirs);
846 }
847 }
848
849 // ----------------------------------------------------------------------------
850 // KDE
851 // ----------------------------------------------------------------------------
852
853
854 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
855 // may be found in either of the following locations
856 //
857 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
858 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
859 //
860 // The format of a .kdelnk file is almost the same as the one used by
861 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
862 // value for the entry "Type"
863
864 // kde writing; see http://webcvs.kde.org/cgi-bin/cvsweb.cgi/~checkout~/kdelibs/kio/DESKTOP_ENTRY_STANDARD
865 // for now write to .kdelnk but should eventually do .desktop instead (in preference??)
866
867 bool wxMimeTypesManagerImpl::CheckKDEDirsExist( const wxString &sOK, const wxString &sTest )
868 {
869 if (sTest.empty())
870 {
871 return wxDir::Exists(sOK);
872 }
873 else
874 {
875 wxString sStart = sOK + wxT("/") + sTest.BeforeFirst(wxT('/'));
876 if (!wxDir::Exists(sStart))
877 wxMkdir(sStart);
878 wxString sEnd = sTest.AfterFirst(wxT('/'));
879 return CheckKDEDirsExist(sStart, sEnd);
880 }
881 }
882
883 bool wxMimeTypesManagerImpl::WriteKDEMimeFile(int index, bool delete_index)
884 {
885 wxMimeTextFile appoutfile, mimeoutfile;
886 wxString sHome = wxGetHomeDir();
887 wxString sTmp = wxT(".kde/share/mimelnk/");
888 wxString sMime = m_aTypes[index];
889 CheckKDEDirsExist(sHome, sTmp + sMime.BeforeFirst(wxT('/')) );
890 sTmp = sHome + wxT('/') + sTmp + sMime + wxT(".kdelnk");
891
892 bool bTemp;
893 bool bMimeExists = mimeoutfile.Open(sTmp);
894 if (!bMimeExists)
895 {
896 bTemp = mimeoutfile.Create(sTmp);
897 // some unknown error eg out of disk space
898 if (!bTemp)
899 return false;
900 }
901
902 sTmp = wxT(".kde/share/applnk/");
903 CheckKDEDirsExist(sHome, sTmp + sMime.AfterFirst(wxT('/')) );
904 sTmp = sHome + wxT('/') + sTmp + sMime.AfterFirst(wxT('/')) + wxT(".kdelnk");
905
906 bool bAppExists;
907 bAppExists = appoutfile.Open(sTmp);
908 if (!bAppExists)
909 {
910 bTemp = appoutfile.Create(sTmp);
911 // some unknown error eg out of disk space
912 if (!bTemp)
913 return false;
914 }
915
916 // fixed data; write if new file
917 if (!bMimeExists)
918 {
919 mimeoutfile.AddLine(wxT("#KDE Config File"));
920 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
921 mimeoutfile.AddLine(wxT("Version=1.0"));
922 mimeoutfile.AddLine(wxT("Type=MimeType"));
923 mimeoutfile.AddLine(wxT("MimeType=") + sMime);
924 }
925
926 if (!bAppExists)
927 {
928 mimeoutfile.AddLine(wxT("#KDE Config File"));
929 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
930 appoutfile.AddLine(wxT("Version=1.0"));
931 appoutfile.AddLine(wxT("Type=Application"));
932 appoutfile.AddLine(wxT("MimeType=") + sMime + wxT(';'));
933 }
934
935 // variable data
936 // ignore locale
937 mimeoutfile.CommentLine(wxT("Comment="));
938 if (!delete_index)
939 mimeoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
940 appoutfile.CommentLine(wxT("Name="));
941 if (!delete_index)
942 appoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
943
944 sTmp = m_aIcons[index];
945 // we can either give the full path, or the shortfilename if its in
946 // one of the directories we search
947 mimeoutfile.CommentLine(wxT("Icon=") );
948 if (!delete_index)
949 mimeoutfile.AddLine(wxT("Icon=") + sTmp );
950 appoutfile.CommentLine(wxT("Icon=") );
951 if (!delete_index)
952 appoutfile.AddLine(wxT("Icon=") + sTmp );
953
954 sTmp = wxT(" ") + m_aExtensions[index];
955
956 wxStringTokenizer tokenizer(sTmp, wxT(" "));
957 sTmp = wxT("Patterns=");
958 mimeoutfile.CommentLine(sTmp);
959 while ( tokenizer.HasMoreTokens() )
960 {
961 // holds an extension; need to change it to *.ext;
962 wxString e = wxT("*.") + tokenizer.GetNextToken() + wxT(";");
963 sTmp = sTmp + e;
964 }
965
966 if (!delete_index)
967 mimeoutfile.AddLine(sTmp);
968
969 wxMimeTypeCommands * entries = m_aEntries[index];
970 // if we don't find open just have an empty string ... FIX this
971 sTmp = entries->GetCommandForVerb(wxT("open"));
972 sTmp.Replace( wxT("%s"), wxT("%f") );
973
974 mimeoutfile.CommentLine(wxT("DefaultApp=") );
975 if (!delete_index)
976 mimeoutfile.AddLine(wxT("DefaultApp=") + sTmp);
977
978 sTmp.Replace( wxT("%f"), wxT("") );
979 appoutfile.CommentLine(wxT("Exec="));
980 if (!delete_index)
981 appoutfile.AddLine(wxT("Exec=") + sTmp);
982
983 if (entries->GetCount() > 1)
984 {
985 //other actions as well as open
986 }
987
988 bTemp = false;
989 if (mimeoutfile.Write())
990 bTemp = true;
991 mimeoutfile.Close();
992 if (appoutfile.Write())
993 bTemp = true;
994 appoutfile.Close();
995
996 return bTemp;
997 }
998
999 void wxMimeTypesManagerImpl::LoadKDELinksForMimeSubtype(const wxString& dirbase,
1000 const wxString& subdir,
1001 const wxString& filename,
1002 const wxArrayString& icondirs)
1003 {
1004 wxMimeTextFile file;
1005 if ( !file.Open(dirbase + filename) )
1006 return;
1007
1008 wxLogTrace(TRACE_MIME, wxT("loading KDE file %s"),
1009 (dirbase + filename).c_str());
1010
1011 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
1012 wxArrayString sExts;
1013 wxString mimetype, mime_desc, strIcon;
1014
1015 int nIndex = file.pIndexOf( wxT("MimeType=") );
1016 if (nIndex == wxNOT_FOUND)
1017 {
1018 // construct mimetype from the directory name and the basename of the
1019 // file (it always has .kdelnk extension)
1020 mimetype << subdir << wxT('/') << filename.BeforeLast( wxT('.') );
1021 }
1022 else
1023 mimetype = file.GetCmd(nIndex);
1024
1025 // first find the description string: it is the value in either "Comment="
1026 // line or "Comment[<locale_name>]=" one
1027 nIndex = wxNOT_FOUND;
1028
1029 wxString comment;
1030
1031 #if wxUSE_INTL
1032 wxLocale *locale = wxGetLocale();
1033 if ( locale )
1034 {
1035 // try "Comment[locale name]" first
1036 comment << wxT("Comment[") + locale->GetName() + wxT("]=");
1037 nIndex = file.pIndexOf(comment);
1038 }
1039 #endif
1040
1041 if ( nIndex == wxNOT_FOUND )
1042 {
1043 comment = wxT("Comment=");
1044 nIndex = file.pIndexOf(comment);
1045 }
1046
1047 if ( nIndex != wxNOT_FOUND )
1048 mime_desc = file.GetCmd(nIndex);
1049 //else: no description
1050
1051 // next find the extensions
1052 wxString mime_extension;
1053
1054 nIndex = file.pIndexOf(wxT("Patterns="));
1055 if ( nIndex != wxNOT_FOUND )
1056 {
1057 wxString exts = file.GetCmd(nIndex);
1058
1059 wxStringTokenizer tokenizer(exts, wxT(";"));
1060 while ( tokenizer.HasMoreTokens() )
1061 {
1062 wxString e = tokenizer.GetNextToken();
1063
1064 // don't support too difficult patterns
1065 if ( e.Left(2) != wxT("*.") )
1066 continue;
1067
1068 if ( !mime_extension.empty() )
1069 {
1070 // separate from the previous ext
1071 mime_extension << wxT(' ');
1072 }
1073
1074 mime_extension << e.Mid(2);
1075 }
1076 }
1077
1078 sExts.Add(mime_extension);
1079
1080 // ok, now we can take care of icon:
1081
1082 nIndex = file.pIndexOf(wxT("Icon="));
1083 if ( nIndex != wxNOT_FOUND )
1084 {
1085 strIcon = file.GetCmd(nIndex);
1086
1087 wxLogTrace(TRACE_MIME, wxT(" icon %s"), strIcon.c_str());
1088
1089 // it could be the real path, but more often a short name
1090 if (!wxFileExists(strIcon))
1091 {
1092 // icon is just the short name
1093 if ( !strIcon.empty() )
1094 {
1095 // we must check if the file exists because it may be stored
1096 // in many locations, at least ~/.kde and $KDEDIR
1097 size_t nDir, nDirs = icondirs.GetCount();
1098 for ( nDir = 0; nDir < nDirs; nDir++ )
1099 {
1100 wxFileName fnameIcon( strIcon );
1101 wxFileName fname( icondirs[nDir], fnameIcon.GetName() );
1102 fname.SetExt( wxT("png") );
1103 if (fname.FileExists())
1104 {
1105 strIcon = fname.GetFullPath();
1106 wxLogTrace(TRACE_MIME, wxT(" iconfile %s"), strIcon.c_str());
1107 break;
1108 }
1109 }
1110 }
1111 }
1112 }
1113
1114 // now look for lines which know about the application
1115 // exec= or DefaultApp=
1116
1117 nIndex = file.pIndexOf(wxT("DefaultApp"));
1118
1119 if ( nIndex == wxNOT_FOUND )
1120 {
1121 // no entry try exec
1122 nIndex = file.pIndexOf(wxT("Exec"));
1123 }
1124
1125 if ( nIndex != wxNOT_FOUND )
1126 {
1127 // we expect %f; others including %F and %U and %u are possible
1128 wxString sTmp = file.GetCmd(nIndex);
1129 if (0 == sTmp.Replace( wxT("%f"), wxT("%s") ))
1130 sTmp = sTmp + wxT(" %s");
1131 entry->AddOrReplaceVerb(wxString(wxT("open")), sTmp );
1132 }
1133
1134 AddToMimeData(mimetype, strIcon, entry, sExts, mime_desc);
1135 }
1136
1137 void wxMimeTypesManagerImpl::LoadKDELinksForMimeType(const wxString& dirbase,
1138 const wxString& subdir,
1139 const wxArrayString& icondirs)
1140 {
1141 wxString dirname = dirbase;
1142 dirname += subdir;
1143 wxDir dir(dirname);
1144 if ( !dir.IsOpened() )
1145 return;
1146
1147 wxLogTrace(TRACE_MIME, wxT("--- Loading from KDE directory %s ---"),
1148 dirname.c_str());
1149
1150 dirname += wxT('/');
1151
1152 wxString filename;
1153 bool cont = dir.GetFirst(&filename, wxT("*.kdelnk"), wxDIR_FILES);
1154 while ( cont )
1155 {
1156 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
1157
1158 cont = dir.GetNext(&filename);
1159 }
1160
1161 // new standard for Gnome and KDE
1162 cont = dir.GetFirst(&filename, wxT("*.desktop"), wxDIR_FILES);
1163 while ( cont )
1164 {
1165 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
1166
1167 cont = dir.GetNext(&filename);
1168 }
1169 }
1170
1171 void wxMimeTypesManagerImpl::LoadKDELinkFilesFromDir(const wxString& dirbase,
1172 const wxArrayString& icondirs)
1173 {
1174 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
1175 wxT("base directory shouldn't end with a slash") );
1176
1177 wxString dirname = dirbase;
1178 dirname << wxT("/mimelnk");
1179
1180 if ( !wxDir::Exists(dirname) )
1181 return;
1182
1183 wxDir dir(dirname);
1184 if ( !dir.IsOpened() )
1185 return;
1186
1187 // we will concatenate it with dir name to get the full path below
1188 dirname += wxT('/');
1189
1190 wxString subdir;
1191 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
1192 while ( cont )
1193 {
1194 LoadKDELinksForMimeType(dirname, subdir, icondirs);
1195
1196 cont = dir.GetNext(&subdir);
1197 }
1198 }
1199
1200 void wxMimeTypesManagerImpl::GetKDEMimeInfo(const wxString& sExtraDir)
1201 {
1202 wxArrayString dirs;
1203 wxArrayString icondirs;
1204
1205 // FIXME: This code is heavily broken. There are three bugs in it:
1206 // 1) it uses only KDEDIR, which is deprecated, instead of using
1207 // list of paths from KDEDIRS and using KDEDIR only if KDEDIRS
1208 // is not set
1209 // 2) it doesn't look into ~/.kde/share/config/kdeglobals where
1210 // user's settings are stored and thus *ignores* user's settings
1211 // instead of respecting them
1212 // 3) it "tries to guess KDEDIR" and "tries a few likely theme
1213 // names", both of which is completely arbitrary; instead, the
1214 // code should give up if KDEDIR(S) is not set and/or the icon
1215 // theme cannot be determined, because it means that the user is
1216 // not using KDE (and thus is not interested in KDE icons anyway)
1217
1218 // the variable $KDEDIR is set when KDE is running
1219 wxString kdedir = wxGetenv( wxT("KDEDIR") );
1220
1221 if (!kdedir.empty())
1222 {
1223 // $(KDEDIR)/share/config/kdeglobals holds info
1224 // the current icons theme
1225 wxFileName configFile( kdedir, wxEmptyString );
1226 configFile.AppendDir( wxT("share") );
1227 configFile.AppendDir( wxT("config") );
1228 configFile.SetName( wxT("kdeglobals") );
1229
1230 wxTextFile config;
1231 if (configFile.FileExists() && config.Open(configFile.GetFullPath()))
1232 {
1233 // $(KDEDIR)/share/config -> $(KDEDIR)/share
1234 configFile.RemoveDir( configFile.GetDirCount() - 1 );
1235 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1236 configFile.AppendDir( wxT("icons") );
1237
1238 // Check for entry
1239 wxString theme(wxT("default.kde"));
1240 size_t cnt = config.GetLineCount();
1241 for (size_t i = 0; i < cnt; i++)
1242 {
1243 if (config[i].StartsWith(wxT("Theme="), &theme/*rest*/))
1244 break;
1245 }
1246
1247 configFile.AppendDir(theme);
1248 }
1249 else
1250 {
1251 // $(KDEDIR)/share/config -> $(KDEDIR)/share
1252 configFile.RemoveDir( configFile.GetDirCount() - 1 );
1253
1254 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1255 configFile.AppendDir( wxT("icons") );
1256
1257 // $(KDEDIR)/share/icons -> $(KDEDIR)/share/icons/default.kde
1258 configFile.AppendDir( wxT("default.kde") );
1259 }
1260
1261 configFile.SetName( wxEmptyString );
1262 configFile.AppendDir( wxT("32x32") );
1263 configFile.AppendDir( wxT("mimetypes") );
1264
1265 // Just try a few likely icons theme names
1266
1267 int pos = configFile.GetDirCount() - 3;
1268
1269 if (!wxDir::Exists(configFile.GetPath()))
1270 {
1271 configFile.RemoveDir( pos );
1272 configFile.InsertDir( pos, wxT("default.kde") );
1273 }
1274
1275 if (!wxDir::Exists(configFile.GetPath()))
1276 {
1277 configFile.RemoveDir( pos );
1278 configFile.InsertDir( pos, wxT("default") );
1279 }
1280
1281 if (!wxDir::Exists(configFile.GetPath()))
1282 {
1283 configFile.RemoveDir( pos );
1284 configFile.InsertDir( pos, wxT("crystalsvg") );
1285 }
1286
1287 if (!wxDir::Exists(configFile.GetPath()))
1288 {
1289 configFile.RemoveDir( pos );
1290 configFile.InsertDir( pos, wxT("crystal") );
1291 }
1292
1293 if (wxDir::Exists(configFile.GetPath()))
1294 icondirs.Add( configFile.GetFullPath() );
1295 }
1296
1297 // settings in ~/.kde have maximal priority
1298 dirs.Add(wxGetHomeDir() + wxT("/.kde/share"));
1299 icondirs.Add(wxGetHomeDir() + wxT("/.kde/share/icons/"));
1300
1301 if (kdedir)
1302 {
1303 dirs.Add( wxString(kdedir) + wxT("/share") );
1304 icondirs.Add( wxString(kdedir) + wxT("/share/icons/") );
1305 }
1306 else
1307 {
1308 // try to guess KDEDIR
1309 dirs.Add(wxT("/usr/share"));
1310 dirs.Add(wxT("/opt/kde/share"));
1311 icondirs.Add(wxT("/usr/share/icons/"));
1312 icondirs.Add(wxT("/usr/X11R6/share/icons/")); // Debian/Corel linux
1313 icondirs.Add(wxT("/opt/kde/share/icons/"));
1314 }
1315
1316 if (!sExtraDir.empty())
1317 dirs.Add(sExtraDir);
1318 icondirs.Add(sExtraDir + wxT("/icons"));
1319
1320 size_t nDirs = dirs.GetCount();
1321 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1322 {
1323 LoadKDELinkFilesFromDir(dirs[nDir], icondirs);
1324 }
1325 }
1326
1327 // ----------------------------------------------------------------------------
1328 // wxFileTypeImpl (Unix)
1329 // ----------------------------------------------------------------------------
1330
1331 wxString wxFileTypeImpl::GetExpandedCommand(const wxString & verb, const wxFileType::MessageParameters& params) const
1332 {
1333 wxString sTmp;
1334 size_t i = 0;
1335 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1336 {
1337 sTmp = m_manager->GetCommand( verb, m_index[i] );
1338 i++;
1339 }
1340
1341 return wxFileType::ExpandCommand(sTmp, params);
1342 }
1343
1344 bool wxFileTypeImpl::GetIcon(wxIconLocation *iconLoc) const
1345 {
1346 wxString sTmp;
1347 size_t i = 0;
1348 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1349 {
1350 sTmp = m_manager->m_aIcons[m_index[i]];
1351 i++;
1352 }
1353
1354 if ( sTmp.empty() )
1355 return false;
1356
1357 if ( iconLoc )
1358 {
1359 iconLoc->SetFileName(sTmp);
1360 }
1361
1362 return true;
1363 }
1364
1365 bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
1366 {
1367 mimeTypes.Clear();
1368 for (size_t i = 0; i < m_index.GetCount(); i++)
1369 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
1370
1371 return true;
1372 }
1373
1374 size_t wxFileTypeImpl::GetAllCommands(wxArrayString *verbs,
1375 wxArrayString *commands,
1376 const wxFileType::MessageParameters& params) const
1377 {
1378 wxString vrb, cmd, sTmp;
1379 size_t count = 0;
1380 wxMimeTypeCommands * sPairs;
1381
1382 // verbs and commands have been cleared already in mimecmn.cpp...
1383 // if we find no entries in the exact match, try the inexact match
1384 for (size_t n = 0; ((count == 0) && (n < m_index.GetCount())); n++)
1385 {
1386 // list of verb = command pairs for this mimetype
1387 sPairs = m_manager->m_aEntries [m_index[n]];
1388 size_t i;
1389 for ( i = 0; i < sPairs->GetCount(); i++ )
1390 {
1391 vrb = sPairs->GetVerb(i);
1392 // some gnome entries have "." inside
1393 vrb = vrb.AfterLast(wxT('.'));
1394 cmd = sPairs->GetCmd(i);
1395 if (! cmd.empty() )
1396 {
1397 cmd = wxFileType::ExpandCommand(cmd, params);
1398 count++;
1399 if ( vrb.IsSameAs(wxT("open")))
1400 {
1401 verbs->Insert(vrb, 0u);
1402 commands ->Insert(cmd, 0u);
1403 }
1404 else
1405 {
1406 verbs->Add(vrb);
1407 commands->Add(cmd);
1408 }
1409 }
1410 }
1411 }
1412
1413 return count;
1414 }
1415
1416 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
1417 {
1418 wxString strExtensions = m_manager->GetExtension(m_index[0]);
1419 extensions.Empty();
1420
1421 // one extension in the space or comma-delimited list
1422 wxString strExt;
1423 for ( const wxChar *p = strExtensions; /* nothing */; p++ )
1424 {
1425 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') )
1426 {
1427 if ( !strExt.empty() )
1428 {
1429 extensions.Add(strExt);
1430 strExt.Empty();
1431 }
1432 //else: repeated spaces
1433 // (shouldn't happen, but it's not that important if it does happen)
1434
1435 if ( *p == wxT('\0') )
1436 break;
1437 }
1438 else if ( *p == wxT('.') )
1439 {
1440 // remove the dot from extension (but only if it's the first char)
1441 if ( !strExt.empty() )
1442 {
1443 strExt += wxT('.');
1444 }
1445 //else: no, don't append it
1446 }
1447 else
1448 {
1449 strExt += *p;
1450 }
1451 }
1452
1453 return true;
1454 }
1455
1456 // set an arbitrary command:
1457 // could adjust the code to ask confirmation if it already exists and
1458 // overwriteprompt is true, but this is currently ignored as *Associate* has
1459 // no overwrite prompt
1460 bool
1461 wxFileTypeImpl::SetCommand(const wxString& cmd,
1462 const wxString& verb,
1463 bool WXUNUSED(overwriteprompt))
1464 {
1465 wxArrayString strExtensions;
1466 wxString strDesc, strIcon;
1467
1468 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1469 entry->Add(verb + wxT("=") + cmd + wxT(" %s "));
1470
1471 wxArrayString strTypes;
1472 GetMimeTypes(strTypes);
1473 if (strTypes.GetCount() < 1)
1474 return false;
1475
1476 size_t i;
1477 bool ok = true;
1478 for (i = 0; i < strTypes.GetCount(); i++)
1479 {
1480 if (!m_manager->DoAssociation(strTypes[i], strIcon, entry, strExtensions, strDesc))
1481 ok = false;
1482 }
1483
1484 return ok;
1485 }
1486
1487 // ignore index on the grouds that we only have one icon in a Unix file
1488 bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int WXUNUSED(index))
1489 {
1490 if (strIcon.empty())
1491 return false;
1492
1493 wxArrayString strExtensions;
1494 wxString strDesc;
1495
1496 wxArrayString strTypes;
1497 GetMimeTypes(strTypes);
1498 if ( strTypes.IsEmpty() )
1499 return false;
1500
1501 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1502 bool ok = true;
1503 for ( size_t i = 0; i < strTypes.GetCount(); i++ )
1504 {
1505 if ( !m_manager->DoAssociation
1506 (
1507 strTypes[i],
1508 strIcon,
1509 entry,
1510 strExtensions,
1511 strDesc
1512 ) )
1513 {
1514 ok = false;
1515 }
1516 }
1517
1518 return ok;
1519 }
1520
1521 // ----------------------------------------------------------------------------
1522 // wxMimeTypesManagerImpl (Unix)
1523 // ----------------------------------------------------------------------------
1524
1525 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1526 {
1527 m_initialized = false;
1528 m_mailcapStylesInited = 0;
1529 }
1530
1531 void wxMimeTypesManagerImpl::InitIfNeeded()
1532 {
1533 if ( !m_initialized )
1534 {
1535 // set the flag first to prevent recursion
1536 m_initialized = true;
1537
1538 #if 1
1539 Initialize();
1540 #else
1541 wxString wm = wxGetenv( wxT("WINDOWMANAGER") );
1542
1543 if (wm.Find( wxT("kde") ) != wxNOT_FOUND)
1544 Initialize( wxMAILCAP_KDE | wxMAILCAP_STANDARD );
1545 else if (wm.Find( wxT("gnome") ) != wxNOT_FOUND)
1546 Initialize( wxMAILCAP_GNOME | wxMAILCAP_STANDARD );
1547 else
1548 Initialize();
1549 #endif
1550 }
1551 }
1552
1553 // read system and user mailcaps and other files
1554 void wxMimeTypesManagerImpl::Initialize(int mailcapStyles,
1555 const wxString& sExtraDir)
1556 {
1557 // read mimecap amd mime.types
1558 if ( (mailcapStyles & wxMAILCAP_NETSCAPE) ||
1559 (mailcapStyles & wxMAILCAP_STANDARD) )
1560 GetMimeInfo(sExtraDir);
1561
1562 // read GNOME tables
1563 if (mailcapStyles & wxMAILCAP_GNOME)
1564 GetGnomeMimeInfo(sExtraDir);
1565
1566 // read KDE tables
1567 if (mailcapStyles & wxMAILCAP_KDE)
1568 GetKDEMimeInfo(sExtraDir);
1569
1570 m_mailcapStylesInited |= mailcapStyles;
1571 }
1572
1573 // clear data so you can read another group of WM files
1574 void wxMimeTypesManagerImpl::ClearData()
1575 {
1576 m_aTypes.Clear();
1577 m_aIcons.Clear();
1578 m_aExtensions.Clear();
1579 m_aDescriptions.Clear();
1580
1581 WX_CLEAR_ARRAY(m_aEntries);
1582 m_aEntries.Empty();
1583
1584 m_mailcapStylesInited = 0;
1585 }
1586
1587 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1588 {
1589 ClearData();
1590 }
1591
1592 void wxMimeTypesManagerImpl::GetMimeInfo(const wxString& sExtraDir)
1593 {
1594 // read this for netscape or Metamail formats
1595
1596 // directories where we look for mailcap and mime.types by default
1597 // used by netscape and pine and other mailers, using 2 different formats!
1598
1599 // (taken from metamail(1) sources)
1600 //
1601 // although RFC 1524 specifies the search path of
1602 // /etc/:/usr/etc:/usr/local/etc only, it doesn't hurt to search in more
1603 // places - OTOH, the RFC also says that this path can be changed with
1604 // MAILCAPS environment variable (containing the colon separated full
1605 // filenames to try) which is not done yet (TODO?)
1606
1607 wxString strHome = wxGetenv(wxT("HOME"));
1608
1609 wxArrayString dirs;
1610 dirs.Add( strHome + wxT("/.") );
1611 dirs.Add( wxT("/etc/") );
1612 dirs.Add( wxT("/usr/etc/") );
1613 dirs.Add( wxT("/usr/local/etc/") );
1614 dirs.Add( wxT("/etc/mail/") );
1615 dirs.Add( wxT("/usr/public/lib/") );
1616 if (!sExtraDir.empty())
1617 dirs.Add( sExtraDir + wxT("/") );
1618
1619 size_t nDirs = dirs.GetCount();
1620 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1621 {
1622 wxString file = dirs[nDir] + wxT("mailcap");
1623 if ( wxFile::Exists(file) )
1624 {
1625 ReadMailcap(file);
1626 }
1627
1628 file = dirs[nDir] + wxT("mime.types");
1629 if ( wxFile::Exists(file) )
1630 {
1631 ReadMimeTypes(file);
1632 }
1633 }
1634 }
1635
1636 bool wxMimeTypesManagerImpl::WriteToMimeTypes(int index, bool delete_index)
1637 {
1638 // check we have the right manager
1639 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD) )
1640 return false;
1641
1642 bool bTemp;
1643 wxString strHome = wxGetenv(wxT("HOME"));
1644
1645 // and now the users mailcap
1646 wxString strUserMailcap = strHome + wxT("/.mime.types");
1647
1648 wxMimeTextFile file;
1649 if ( wxFile::Exists(strUserMailcap) )
1650 {
1651 bTemp = file.Open(strUserMailcap);
1652 }
1653 else
1654 {
1655 if (delete_index)
1656 return false;
1657
1658 bTemp = file.Create(strUserMailcap);
1659 }
1660
1661 if (bTemp)
1662 {
1663 int nIndex;
1664 // test for netscape's header and return false if its found
1665 nIndex = file.pIndexOf(wxT("#--Netscape"));
1666 if (nIndex != wxNOT_FOUND)
1667 {
1668 wxASSERT_MSG(false,wxT("Error in .mime.types \nTrying to mix Netscape and Metamail formats\nFile not modiifed"));
1669 return false;
1670 }
1671
1672 // write it in alternative format
1673 // get rid of unwanted entries
1674 wxString strType = m_aTypes[index];
1675 nIndex = file.pIndexOf(strType);
1676
1677 // get rid of all the unwanted entries...
1678 if (nIndex != wxNOT_FOUND)
1679 file.CommentLine(nIndex);
1680
1681 if (!delete_index)
1682 {
1683 // add the new entries in
1684 wxString sTmp = strType.Append( wxT(' '), 40 - strType.Len() );
1685 sTmp = sTmp + m_aExtensions[index];
1686 file.AddLine(sTmp);
1687 }
1688
1689 bTemp = file.Write();
1690 file.Close();
1691 }
1692
1693 return bTemp;
1694 }
1695
1696 bool wxMimeTypesManagerImpl::WriteToNSMimeTypes(int index, bool delete_index)
1697 {
1698 //check we have the right managers
1699 if (! ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) )
1700 return false;
1701
1702 bool bTemp;
1703 wxString strHome = wxGetenv(wxT("HOME"));
1704
1705 // and now the users mailcap
1706 wxString strUserMailcap = strHome + wxT("/.mime.types");
1707
1708 wxMimeTextFile file;
1709 if ( wxFile::Exists(strUserMailcap) )
1710 {
1711 bTemp = file.Open(strUserMailcap);
1712 }
1713 else
1714 {
1715 if (delete_index)
1716 return false;
1717
1718 bTemp = file.Create(strUserMailcap);
1719 }
1720
1721 if (bTemp)
1722 {
1723 // write it in the format that Netscape uses
1724 int nIndex;
1725 // test for netscape's header and insert if required...
1726 // this is a comment so use true
1727 nIndex = file.pIndexOf(wxT("#--Netscape"), true);
1728 if (nIndex == wxNOT_FOUND)
1729 {
1730 // either empty file or metamail format
1731 // at present we can't cope with mixed formats, so exit to preseve
1732 // metamail entreies
1733 if (file.GetLineCount() > 0)
1734 {
1735 wxASSERT_MSG(false, wxT(".mime.types File not in Netscape format\nNo entries written to\n.mime.types or to .mailcap"));
1736 return false;
1737 }
1738
1739 file.InsertLine(wxT( "#--Netscape Communications Corporation MIME Information" ), 0);
1740 nIndex = 0;
1741 }
1742
1743 wxString strType = wxT("type=") + m_aTypes[index];
1744 nIndex = file.pIndexOf(strType);
1745
1746 // get rid of all the unwanted entries...
1747 if (nIndex != wxNOT_FOUND)
1748 {
1749 wxString sOld = file[nIndex];
1750 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1751 {
1752 file.CommentLine(nIndex);
1753 sOld = file[nIndex];
1754
1755 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mime.types line '%d %s' ---"), nIndex, sOld.c_str());
1756
1757 nIndex++;
1758 }
1759
1760 if (nIndex < (int) file.GetLineCount())
1761 file.CommentLine(nIndex);
1762 }
1763 else
1764 nIndex = (int) file.GetLineCount();
1765
1766 wxString sTmp = strType + wxT(" \\");
1767 if (!delete_index)
1768 file.InsertLine(sTmp, nIndex);
1769
1770 if ( ! m_aDescriptions.Item(index).empty() )
1771 {
1772 sTmp = wxT("desc=\"") + m_aDescriptions[index]+ wxT("\" \\"); //.trim ??
1773 if (!delete_index)
1774 {
1775 nIndex++;
1776 file.InsertLine(sTmp, nIndex);
1777 }
1778 }
1779
1780 wxString sExts = m_aExtensions.Item(index);
1781 sTmp = wxT("exts=\"") + sExts.Trim(false).Trim() + wxT("\"");
1782 if (!delete_index)
1783 {
1784 nIndex++;
1785 file.InsertLine(sTmp, nIndex);
1786 }
1787
1788 bTemp = file.Write();
1789 file.Close();
1790 }
1791
1792 return bTemp;
1793 }
1794
1795 bool wxMimeTypesManagerImpl::WriteToMailCap(int index, bool delete_index)
1796 {
1797 //check we have the right managers
1798 if ( !( ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) ||
1799 ( m_mailcapStylesInited & wxMAILCAP_STANDARD) ) )
1800 return false;
1801
1802 bool bTemp = false;
1803 wxString strHome = wxGetenv(wxT("HOME"));
1804
1805 // and now the users mailcap
1806 wxString strUserMailcap = strHome + wxT("/.mailcap");
1807
1808 wxMimeTextFile file;
1809 if ( wxFile::Exists(strUserMailcap) )
1810 {
1811 bTemp = file.Open(strUserMailcap);
1812 }
1813 else
1814 {
1815 if (delete_index)
1816 return false;
1817
1818 bTemp = file.Create(strUserMailcap);
1819 }
1820
1821 if (bTemp)
1822 {
1823 // now got a file we can write to ....
1824 wxMimeTypeCommands * entries = m_aEntries[index];
1825 size_t iOpen;
1826 wxString sCmd = entries->GetCommandForVerb(wxT("open"), &iOpen);
1827 wxString sTmp;
1828
1829 sTmp = m_aTypes[index];
1830 wxString sOld;
1831 int nIndex = file.pIndexOf(sTmp);
1832
1833 // get rid of all the unwanted entries...
1834 if (nIndex == wxNOT_FOUND)
1835 {
1836 nIndex = (int) file.GetLineCount();
1837 }
1838 else
1839 {
1840 sOld = file[nIndex];
1841 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mailcap line '%d' ---"), nIndex);
1842
1843 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1844 {
1845 file.CommentLine(nIndex);
1846 if (nIndex < (int) file.GetLineCount())
1847 sOld = sOld + file[nIndex];
1848 }
1849
1850 if (nIndex < (int)
1851 file.GetLineCount()) file.CommentLine(nIndex);
1852 }
1853
1854 sTmp = sTmp + wxT(";") + sCmd; //includes wxT(" %s ");
1855
1856 // write it in the format that Netscape uses (default)
1857 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD ) )
1858 {
1859 if (! delete_index)
1860 file.InsertLine(sTmp, nIndex);
1861 nIndex++;
1862 }
1863 else
1864 {
1865 // write extended format
1866
1867 // TODO - FIX this code:
1868 // ii) lost entries
1869 // sOld holds all the entries, but our data store only has some
1870 // eg test= is not stored
1871
1872 // so far we have written the mimetype and command out
1873 wxStringTokenizer sT(sOld, wxT(";\\"));
1874 if (sT.CountTokens() > 2)
1875 {
1876 // first one mimetype; second one command, rest unknown...
1877 wxString s;
1878 s = sT.GetNextToken();
1879 s = sT.GetNextToken();
1880
1881 // first unknown
1882 s = sT.GetNextToken();
1883 while ( ! s.empty() )
1884 {
1885 bool bKnownToken = false;
1886 if (s.Contains(wxT("description=")))
1887 bKnownToken = true;
1888 if (s.Contains(wxT("x11-bitmap=")))
1889 bKnownToken = true;
1890
1891 size_t i;
1892 for (i=0; i < entries->GetCount(); i++)
1893 {
1894 if (s.Contains(entries->GetVerb(i)))
1895 bKnownToken = true;
1896 }
1897
1898 if (!bKnownToken)
1899 {
1900 sTmp = sTmp + wxT("; \\");
1901 file.InsertLine(sTmp, nIndex);
1902 sTmp = s;
1903 }
1904
1905 s = sT.GetNextToken();
1906 }
1907 }
1908
1909 if (! m_aDescriptions[index].empty() )
1910 {
1911 sTmp = sTmp + wxT("; \\");
1912 file.InsertLine(sTmp, nIndex);
1913 nIndex++;
1914 sTmp = wxT(" description=\"") + m_aDescriptions[index] + wxT("\"");
1915 }
1916
1917 if (! m_aIcons[index].empty() )
1918 {
1919 sTmp = sTmp + wxT("; \\");
1920 file.InsertLine(sTmp, nIndex);
1921 nIndex++;
1922 sTmp = wxT(" x11-bitmap=\"") + m_aIcons[index] + wxT("\"");
1923 }
1924
1925 if ( entries->GetCount() > 1 )
1926 {
1927 size_t i;
1928 for (i=0; i < entries->GetCount(); i++)
1929 if ( i != iOpen )
1930 {
1931 sTmp = sTmp + wxT("; \\");
1932 file.InsertLine(sTmp, nIndex);
1933 nIndex++;
1934 sTmp = wxT(" ") + entries->GetVerbCmd(i);
1935 }
1936 }
1937
1938 file.InsertLine(sTmp, nIndex);
1939 nIndex++;
1940 }
1941
1942 bTemp = file.Write();
1943 file.Close();
1944 }
1945
1946 return bTemp;
1947 }
1948
1949 wxFileType * wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
1950 {
1951 InitIfNeeded();
1952
1953 wxString strType = ftInfo.GetMimeType();
1954 wxString strDesc = ftInfo.GetDescription();
1955 wxString strIcon = ftInfo.GetIconFile();
1956
1957 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1958
1959 if ( ! ftInfo.GetOpenCommand().empty())
1960 entry->Add(wxT("open=") + ftInfo.GetOpenCommand() + wxT(" %s "));
1961 if ( ! ftInfo.GetPrintCommand().empty())
1962 entry->Add(wxT("print=") + ftInfo.GetPrintCommand() + wxT(" %s "));
1963
1964 // now find where these extensions are in the data store and remove them
1965 wxArrayString sA_Exts = ftInfo.GetExtensions();
1966 wxString sExt, sExtStore;
1967 size_t i, nIndex;
1968 for (i=0; i < sA_Exts.GetCount(); i++)
1969 {
1970 sExt = sA_Exts.Item(i);
1971
1972 // clean up to just a space before and after
1973 sExt.Trim().Trim(false);
1974 sExt = wxT(' ') + sExt + wxT(' ');
1975 for (nIndex = 0; nIndex < m_aExtensions.GetCount(); nIndex++)
1976 {
1977 sExtStore = m_aExtensions.Item(nIndex);
1978 if (sExtStore.Replace(sExt, wxT(" ") ) > 0)
1979 m_aExtensions.Item(nIndex) = sExtStore;
1980 }
1981 }
1982
1983 if ( !DoAssociation(strType, strIcon, entry, sA_Exts, strDesc) )
1984 return NULL;
1985
1986 return GetFileTypeFromMimeType(strType);
1987 }
1988
1989 bool wxMimeTypesManagerImpl::DoAssociation(const wxString& strType,
1990 const wxString& strIcon,
1991 wxMimeTypeCommands *entry,
1992 const wxArrayString& strExtensions,
1993 const wxString& strDesc)
1994 {
1995 int nIndex = AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
1996
1997 if ( nIndex == wxNOT_FOUND )
1998 return false;
1999
2000 return WriteMimeInfo(nIndex, false);
2001 }
2002
2003 bool wxMimeTypesManagerImpl::WriteMimeInfo(int nIndex, bool delete_mime )
2004 {
2005 bool ok = true;
2006
2007 if ( m_mailcapStylesInited & wxMAILCAP_STANDARD )
2008 {
2009 // write in metamail format;
2010 if (WriteToMimeTypes(nIndex, delete_mime) )
2011 if ( WriteToMailCap(nIndex, delete_mime) )
2012 ok = false;
2013 }
2014
2015 if ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE )
2016 {
2017 // write in netsacpe format;
2018 if (WriteToNSMimeTypes(nIndex, delete_mime) )
2019 if ( WriteToMailCap(nIndex, delete_mime) )
2020 ok = false;
2021 }
2022
2023 if (m_mailcapStylesInited & wxMAILCAP_GNOME)
2024 {
2025 // write in Gnome format;
2026 if (WriteGnomeMimeFile(nIndex, delete_mime) )
2027 if (WriteGnomeKeyFile(nIndex, delete_mime) )
2028 ok = false;
2029 }
2030
2031 if (m_mailcapStylesInited & wxMAILCAP_KDE)
2032 {
2033 // write in KDE format;
2034 if (WriteKDEMimeFile(nIndex, delete_mime) )
2035 ok = false;
2036 }
2037
2038 return ok;
2039 }
2040
2041 int wxMimeTypesManagerImpl::AddToMimeData(const wxString& strType,
2042 const wxString& strIcon,
2043 wxMimeTypeCommands *entry,
2044 const wxArrayString& strExtensions,
2045 const wxString& strDesc,
2046 bool replaceExisting)
2047 {
2048 InitIfNeeded();
2049
2050 // ensure mimetype is always lower case
2051 wxString mimeType = strType.Lower();
2052
2053 // is this a known MIME type?
2054 int nIndex = m_aTypes.Index(mimeType);
2055 if ( nIndex == wxNOT_FOUND )
2056 {
2057 // new file type
2058 m_aTypes.Add(mimeType);
2059 m_aIcons.Add(strIcon);
2060 m_aEntries.Add(entry ? entry : new wxMimeTypeCommands);
2061
2062 // change nIndex so we can use it below to add the extensions
2063 m_aExtensions.Add(wxEmptyString);
2064 nIndex = m_aExtensions.size() - 1;
2065
2066 m_aDescriptions.Add(strDesc);
2067 }
2068 else // yes, we already have it
2069 {
2070 if ( replaceExisting )
2071 {
2072 // if new description change it
2073 if ( !strDesc.empty())
2074 m_aDescriptions[nIndex] = strDesc;
2075
2076 // if new icon change it
2077 if ( !strIcon.empty())
2078 m_aIcons[nIndex] = strIcon;
2079
2080 if ( entry )
2081 {
2082 delete m_aEntries[nIndex];
2083 m_aEntries[nIndex] = entry;
2084 }
2085 }
2086 else // add data we don't already have ...
2087 {
2088 // if new description add only if none
2089 if ( m_aDescriptions[nIndex].empty() )
2090 m_aDescriptions[nIndex] = strDesc;
2091
2092 // if new icon and no existing icon
2093 if ( m_aIcons[nIndex].empty() )
2094 m_aIcons[nIndex] = strIcon;
2095
2096 // add any new entries...
2097 if ( entry )
2098 {
2099 wxMimeTypeCommands *entryOld = m_aEntries[nIndex];
2100
2101 size_t count = entry->GetCount();
2102 for ( size_t i = 0; i < count; i++ )
2103 {
2104 const wxString& verb = entry->GetVerb(i);
2105 if ( !entryOld->HasVerb(verb) )
2106 {
2107 entryOld->AddOrReplaceVerb(verb, entry->GetCmd(i));
2108 }
2109 }
2110
2111 // as we don't store it anywhere, it won't be deleted later as
2112 // usual -- do it immediately instead
2113 delete entry;
2114 }
2115 }
2116 }
2117
2118 // always add the extensions to this mimetype
2119 wxString& exts = m_aExtensions[nIndex];
2120
2121 // add all extensions we don't have yet
2122 size_t count = strExtensions.GetCount();
2123 for ( size_t i = 0; i < count; i++ )
2124 {
2125 wxString ext = strExtensions[i] + wxT(' ');
2126
2127 if ( exts.Find(ext) == wxNOT_FOUND )
2128 {
2129 exts += ext;
2130 }
2131 }
2132
2133 // check data integrity
2134 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
2135 m_aTypes.Count() == m_aExtensions.Count() &&
2136 m_aTypes.Count() == m_aIcons.Count() &&
2137 m_aTypes.Count() == m_aDescriptions.Count() );
2138
2139 return nIndex;
2140 }
2141
2142 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
2143 {
2144 if (ext.empty() )
2145 return NULL;
2146
2147 InitIfNeeded();
2148
2149 size_t count = m_aExtensions.GetCount();
2150 for ( size_t n = 0; n < count; n++ )
2151 {
2152 wxStringTokenizer tk(m_aExtensions[n], wxT(' '));
2153
2154 while ( tk.HasMoreTokens() )
2155 {
2156 // consider extensions as not being case-sensitive
2157 if ( tk.GetNextToken().IsSameAs(ext, false /* no case */) )
2158 {
2159 // found
2160 wxFileType *fileType = new wxFileType;
2161 fileType->m_impl->Init(this, n);
2162
2163 return fileType;
2164 }
2165 }
2166 }
2167
2168 return NULL;
2169 }
2170
2171 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
2172 {
2173 InitIfNeeded();
2174
2175 wxFileType * fileType = NULL;
2176 // mime types are not case-sensitive
2177 wxString mimetype(mimeType);
2178 mimetype.MakeLower();
2179
2180 // first look for an exact match
2181 int index = m_aTypes.Index(mimetype);
2182 if ( index != wxNOT_FOUND )
2183 {
2184 fileType = new wxFileType;
2185 fileType->m_impl->Init(this, index);
2186 }
2187
2188 // then try to find "text/*" as match for "text/plain" (for example)
2189 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
2190 // the whole string - ok.
2191
2192 index = wxNOT_FOUND;
2193 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
2194
2195 size_t nCount = m_aTypes.Count();
2196 for ( size_t n = 0; n < nCount; n++ )
2197 {
2198 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
2199 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") )
2200 {
2201 index = n;
2202 break;
2203 }
2204 }
2205
2206 if ( index != wxNOT_FOUND )
2207 {
2208 // don't throw away fileType that was already found
2209 if (!fileType)
2210 fileType = new wxFileType;
2211 fileType->m_impl->Init(this, index);
2212 }
2213
2214 return fileType;
2215 }
2216
2217 wxString wxMimeTypesManagerImpl::GetCommand(const wxString & verb, size_t nIndex) const
2218 {
2219 wxString command, testcmd, sV, sTmp;
2220 sV = verb + wxT("=");
2221
2222 // list of verb = command pairs for this mimetype
2223 wxMimeTypeCommands * sPairs = m_aEntries [nIndex];
2224
2225 size_t i;
2226 for ( i = 0; i < sPairs->GetCount (); i++ )
2227 {
2228 sTmp = sPairs->GetVerbCmd (i);
2229 if ( sTmp.Contains(sV) )
2230 command = sTmp.AfterFirst(wxT('='));
2231 }
2232
2233 return command;
2234 }
2235
2236 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
2237 {
2238 InitIfNeeded();
2239
2240 wxString extensions;
2241 const wxArrayString& exts = filetype.GetExtensions();
2242 size_t nExts = exts.GetCount();
2243 for ( size_t nExt = 0; nExt < nExts; nExt++ )
2244 {
2245 if ( nExt > 0 )
2246 extensions += wxT(' ');
2247
2248 extensions += exts[nExt];
2249 }
2250
2251 AddMimeTypeInfo(filetype.GetMimeType(),
2252 extensions,
2253 filetype.GetDescription());
2254
2255 AddMailcapInfo(filetype.GetMimeType(),
2256 filetype.GetOpenCommand(),
2257 filetype.GetPrintCommand(),
2258 wxT(""),
2259 filetype.GetDescription());
2260 }
2261
2262 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
2263 const wxString& strExtensions,
2264 const wxString& strDesc)
2265 {
2266 // reading mailcap may find image/* , while
2267 // reading mime.types finds image/gif and no match is made
2268 // this means all the get functions don't work fix this
2269 wxString strIcon;
2270 wxString sTmp = strExtensions;
2271
2272 wxArrayString sExts;
2273 sTmp.Trim().Trim(false);
2274
2275 while (!sTmp.empty())
2276 {
2277 sExts.Add(sTmp.AfterLast(wxT(' ')));
2278 sTmp = sTmp.BeforeLast(wxT(' '));
2279 }
2280
2281 AddToMimeData(strMimeType, strIcon, NULL, sExts, strDesc, true);
2282 }
2283
2284 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
2285 const wxString& strOpenCmd,
2286 const wxString& strPrintCmd,
2287 const wxString& strTest,
2288 const wxString& strDesc)
2289 {
2290 InitIfNeeded();
2291
2292 wxMimeTypeCommands *entry = new wxMimeTypeCommands;
2293 entry->Add(wxT("open=") + strOpenCmd);
2294 entry->Add(wxT("print=") + strPrintCmd);
2295 entry->Add(wxT("test=") + strTest);
2296
2297 wxString strIcon;
2298 wxArrayString strExtensions;
2299
2300 AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
2301 }
2302
2303 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
2304 {
2305 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
2306 strFileName.c_str());
2307
2308 wxTextFile file(strFileName);
2309 #if defined(__WXGTK20__) && wxUSE_UNICODE
2310 if ( !file.Open(wxConvUTF8) )
2311 #else
2312 if ( !file.Open() )
2313 #endif
2314 return false;
2315
2316 // the information we extract
2317 wxString strMimeType, strDesc, strExtensions;
2318
2319 size_t nLineCount = file.GetLineCount();
2320 const wxChar *pc = NULL;
2321 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2322 {
2323 if ( pc == NULL )
2324 {
2325 // now we're at the start of the line
2326 pc = file[nLine].c_str();
2327 }
2328 else
2329 {
2330 // we didn't finish with the previous line yet
2331 nLine--;
2332 }
2333
2334 // skip whitespace
2335 while ( wxIsspace(*pc) )
2336 pc++;
2337
2338 // comment or blank line?
2339 if ( *pc == wxT('#') || !*pc )
2340 {
2341 // skip the whole line
2342 pc = NULL;
2343 continue;
2344 }
2345
2346 // detect file format
2347 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
2348 if ( pEqualSign == NULL )
2349 {
2350 // brief format
2351 // ------------
2352
2353 // first field is mime type
2354 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ )
2355 {
2356 strMimeType += *pc;
2357 }
2358
2359 // skip whitespace
2360 while ( wxIsspace(*pc) )
2361 pc++;
2362
2363 // take all the rest of the string
2364 strExtensions = pc;
2365
2366 // no description...
2367 strDesc.Empty();
2368 }
2369 else
2370 {
2371 // expanded format
2372 // ---------------
2373
2374 // the string on the left of '=' is the field name
2375 wxString strLHS(pc, pEqualSign - pc);
2376
2377 // eat whitespace
2378 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
2379 ;
2380
2381 const wxChar *pEnd;
2382 if ( *pc == wxT('"') )
2383 {
2384 // the string is quoted and ends at the matching quote
2385 pEnd = wxStrchr(++pc, wxT('"'));
2386 if ( pEnd == NULL )
2387 {
2388 wxLogWarning(wxT("Mime.types file %s, line %d: unterminated quoted string."),
2389 strFileName.c_str(), nLine + 1);
2390 }
2391 }
2392 else
2393 {
2394 // unquoted string ends at the first space or at the end of
2395 // line
2396 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
2397 ;
2398 }
2399
2400 // now we have the RHS (field value)
2401 wxString strRHS(pc, pEnd - pc);
2402
2403 // check what follows this entry
2404 if ( *pEnd == wxT('"') )
2405 {
2406 // skip this quote
2407 pEnd++;
2408 }
2409
2410 for ( pc = pEnd; wxIsspace(*pc); pc++ )
2411 ;
2412
2413 // if there is something left, it may be either a '\\' to continue
2414 // the line or the next field of the same entry
2415 bool entryEnded = *pc == wxT('\0');
2416 bool nextFieldOnSameLine = false;
2417 if ( !entryEnded )
2418 {
2419 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
2420 }
2421
2422 // now see what we got
2423 if ( strLHS == wxT("type") )
2424 {
2425 strMimeType = strRHS;
2426 }
2427 else if ( strLHS.StartsWith(wxT("desc")) )
2428 {
2429 strDesc = strRHS;
2430 }
2431 else if ( strLHS == wxT("exts") )
2432 {
2433 strExtensions = strRHS;
2434 }
2435 else if ( strLHS == wxT("icon") )
2436 {
2437 // this one is simply ignored: it usually refers to Netscape
2438 // built in icons which are useless for us anyhow
2439 }
2440 else if ( !strLHS.StartsWith(wxT("x-")) )
2441 {
2442 // we suppose that all fields starting with "X-" are
2443 // unregistered extensions according to the standard practice,
2444 // but it may be worth telling the user about other junk in
2445 // his mime.types file
2446 wxLogWarning(wxT("Unknown field in file %s, line %d: '%s'."),
2447 strFileName.c_str(), nLine + 1, strLHS.c_str());
2448 }
2449
2450 if ( !entryEnded )
2451 {
2452 if ( !nextFieldOnSameLine )
2453 pc = NULL;
2454 //else: don't reset it
2455
2456 // as we don't reset strMimeType, the next field in this entry
2457 // will be interpreted correctly.
2458
2459 continue;
2460 }
2461 }
2462
2463 // depending on the format (Mosaic or Netscape) either space or comma
2464 // is used to separate the extensions
2465 strExtensions.Replace(wxT(","), wxT(" "));
2466
2467 // also deal with the leading dot
2468 if ( !strExtensions.empty() && strExtensions[0u] == wxT('.') )
2469 {
2470 strExtensions.erase(0, 1);
2471 }
2472
2473 wxLogTrace(TRACE_MIME, wxT("mime.types: '%s' => '%s' (%s)"),
2474 strExtensions.c_str(),
2475 strMimeType.c_str(),
2476 strDesc.c_str());
2477
2478 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
2479
2480 // finished with this line
2481 pc = NULL;
2482 }
2483
2484 return true;
2485 }
2486
2487 // ----------------------------------------------------------------------------
2488 // UNIX mailcap files parsing
2489 // ----------------------------------------------------------------------------
2490
2491 // the data for a single MIME type
2492 struct MailcapLineData
2493 {
2494 // field values
2495 wxString type,
2496 cmdOpen,
2497 test,
2498 icon,
2499 desc;
2500
2501 wxArrayString verbs,
2502 commands;
2503
2504 // flags
2505 bool testfailed,
2506 needsterminal,
2507 copiousoutput;
2508
2509 MailcapLineData() { testfailed = needsterminal = copiousoutput = false; }
2510 };
2511
2512 // process a non-standard (i.e. not the first or second one) mailcap field
2513 bool
2514 wxMimeTypesManagerImpl::ProcessOtherMailcapField(MailcapLineData& data,
2515 const wxString& curField)
2516 {
2517 if ( curField.empty() )
2518 {
2519 // we don't care
2520 return true;
2521 }
2522
2523 // is this something of the form foo=bar?
2524 const wxChar *pEq = wxStrchr(curField, wxT('='));
2525 if ( pEq != NULL )
2526 {
2527 // split "LHS = RHS" in 2
2528 wxString lhs = curField.BeforeFirst(wxT('=')),
2529 rhs = curField.AfterFirst(wxT('='));
2530
2531 lhs.Trim(true); // from right
2532 rhs.Trim(false); // from left
2533
2534 // it might be quoted
2535 if ( !rhs.empty() && rhs[0u] == wxT('"') && rhs.Last() == wxT('"') )
2536 {
2537 rhs = rhs.Mid(1, rhs.length() - 2);
2538 }
2539
2540 // is it a command verb or something else?
2541 if ( lhs == wxT("test") )
2542 {
2543 if ( wxSystem(rhs) == 0 )
2544 {
2545 // ok, test passed
2546 wxLogTrace(TRACE_MIME_TEST,
2547 wxT("Test '%s' for mime type '%s' succeeded."),
2548 rhs.c_str(), data.type.c_str());
2549 }
2550 else
2551 {
2552 wxLogTrace(TRACE_MIME_TEST,
2553 wxT("Test '%s' for mime type '%s' failed, skipping."),
2554 rhs.c_str(), data.type.c_str());
2555
2556 data.testfailed = true;
2557 }
2558 }
2559 else if ( lhs == wxT("desc") )
2560 {
2561 data.desc = rhs;
2562 }
2563 else if ( lhs == wxT("x11-bitmap") )
2564 {
2565 data.icon = rhs;
2566 }
2567 else if ( lhs == wxT("notes") )
2568 {
2569 // ignore
2570 }
2571 else // not a (recognized) special case, must be a verb (e.g. "print")
2572 {
2573 data.verbs.Add(lhs);
2574 data.commands.Add(rhs);
2575 }
2576 }
2577 else // '=' not found
2578 {
2579 // so it must be a simple flag
2580 if ( curField == wxT("needsterminal") )
2581 {
2582 data.needsterminal = true;
2583 }
2584 else if ( curField == wxT("copiousoutput"))
2585 {
2586 // copiousoutput impies that the viewer is a console program
2587 data.needsterminal =
2588 data.copiousoutput = true;
2589 }
2590 else if ( !IsKnownUnimportantField(curField) )
2591 {
2592 return false;
2593 }
2594 }
2595
2596 return true;
2597 }
2598
2599 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
2600 bool fallback)
2601 {
2602 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
2603 strFileName.c_str());
2604
2605 wxTextFile file(strFileName);
2606 #if defined(__WXGTK20__) && wxUSE_UNICODE
2607 if ( !file.Open(wxConvUTF8) )
2608 #else
2609 if ( !file.Open() )
2610 #endif
2611 return false;
2612
2613 // indices of MIME types (in m_aTypes) we already found in this file
2614 //
2615 // (see the comments near the end of function for the reason we need this)
2616 wxArrayInt aIndicesSeenHere;
2617
2618 // accumulator for the current field
2619 wxString curField;
2620 curField.reserve(1024);
2621
2622 size_t nLineCount = file.GetLineCount();
2623 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2624 {
2625 // now we're at the start of the line
2626 const wxChar *pc = file[nLine].c_str();
2627
2628 // skip whitespace
2629 while ( wxIsspace(*pc) )
2630 pc++;
2631
2632 // comment or empty string?
2633 if ( *pc == wxT('#') || *pc == wxT('\0') )
2634 continue;
2635
2636 // no, do parse
2637 // ------------
2638
2639 // what field are we currently in? The first 2 are fixed and there may
2640 // be an arbitrary number of other fields parsed by
2641 // ProcessOtherMailcapField()
2642 //
2643 // the first field is the MIME type
2644 enum
2645 {
2646 Field_Type,
2647 Field_OpenCmd,
2648 Field_Other
2649 }
2650 currentToken = Field_Type;
2651
2652 // the flags and field values on the current line
2653 MailcapLineData data;
2654
2655 bool cont = true;
2656 while ( cont )
2657 {
2658 switch ( *pc )
2659 {
2660 case wxT('\\'):
2661 // interpret the next character literally (notice that
2662 // backslash can be used for line continuation)
2663 if ( *++pc == wxT('\0') )
2664 {
2665 // fetch the next line if there is one
2666 if ( nLine == nLineCount - 1 )
2667 {
2668 // something is wrong, bail out
2669 cont = false;
2670
2671 wxLogDebug(wxT("Mailcap file %s, line %lu: '\\' on the end of the last line ignored."),
2672 strFileName.c_str(),
2673 (unsigned long)nLine + 1);
2674 }
2675 else
2676 {
2677 // pass to the beginning of the next line
2678 pc = file[++nLine].c_str();
2679
2680 // skip pc++ at the end of the loop
2681 continue;
2682 }
2683 }
2684 else
2685 {
2686 // just a normal character
2687 curField += *pc;
2688 }
2689 break;
2690
2691 case wxT('\0'):
2692 cont = false; // end of line reached, exit the loop
2693
2694 // fall through to still process this field
2695
2696 case wxT(';'):
2697 // trim whitespaces from both sides
2698 curField.Trim(true).Trim(false);
2699
2700 switch ( currentToken )
2701 {
2702 case Field_Type:
2703 data.type = curField.Lower();
2704 if ( data.type.empty() )
2705 {
2706 // I don't think that this is a valid mailcap
2707 // entry, but try to interpret it somehow
2708 data.type = wxT('*');
2709 }
2710
2711 if ( data.type.Find(wxT('/')) == wxNOT_FOUND )
2712 {
2713 // we interpret "type" as "type/*"
2714 data.type += wxT("/*");
2715 }
2716
2717 currentToken = Field_OpenCmd;
2718 break;
2719
2720 case Field_OpenCmd:
2721 data.cmdOpen = curField;
2722
2723 currentToken = Field_Other;
2724 break;
2725
2726 case Field_Other:
2727 if ( !ProcessOtherMailcapField(data, curField) )
2728 {
2729 // don't flood the user with error messages if
2730 // we don't understand something in his
2731 // mailcap, but give them in debug mode because
2732 // this might be useful for the programmer
2733 wxLogDebug
2734 (
2735 wxT("Mailcap file %s, line %lu: unknown field '%s' for the MIME type '%s' ignored."),
2736 strFileName.c_str(),
2737 (unsigned long)nLine + 1,
2738 curField.c_str(),
2739 data.type.c_str()
2740 );
2741 }
2742 else if ( data.testfailed )
2743 {
2744 // skip this entry entirely
2745 cont = false;
2746 }
2747
2748 // it already has this value
2749 //currentToken = Field_Other;
2750 break;
2751
2752 default:
2753 wxFAIL_MSG(wxT("unknown field type in mailcap"));
2754 }
2755
2756 // next token starts immediately after ';'
2757 curField.Empty();
2758 break;
2759
2760 default:
2761 curField += *pc;
2762 }
2763
2764 // continue in the same line
2765 pc++;
2766 }
2767
2768 // we read the entire entry, check what have we got
2769 // ------------------------------------------------
2770
2771 // check that we really read something reasonable
2772 if ( currentToken < Field_Other )
2773 {
2774 wxLogWarning(wxT("Mailcap file %s, line %d: incomplete entry ignored."),
2775 strFileName.c_str(), nLine + 1);
2776
2777 continue;
2778 }
2779
2780 // if the test command failed, it's as if the entry were not there at all
2781 if ( data.testfailed )
2782 {
2783 continue;
2784 }
2785
2786 // support for flags:
2787 // 1. create an xterm for 'needsterminal'
2788 // 2. append "| $PAGER" for 'copiousoutput'
2789 //
2790 // Note that the RFC says that having both needsterminal and
2791 // copiousoutput is probably a mistake, so it seems that running
2792 // programs with copiousoutput inside an xterm as it is done now
2793 // is a bad idea (FIXME)
2794 if ( data.copiousoutput )
2795 {
2796 const wxChar *p = wxGetenv(wxT("PAGER"));
2797 data.cmdOpen << wxT(" | ") << (p ? p : wxT("more"));
2798 }
2799
2800 if ( data.needsterminal )
2801 {
2802 data.cmdOpen = wxString::Format(wxT("xterm -e sh -c '%s'"),
2803 data.cmdOpen.c_str());
2804 }
2805
2806 if ( !data.cmdOpen.empty() )
2807 {
2808 data.verbs.Insert(wxT("open"), 0);
2809 data.commands.Insert(data.cmdOpen, 0);
2810 }
2811
2812 // we have to decide whether the new entry should replace any entries
2813 // for the same MIME type we had previously found or not
2814 bool overwrite;
2815
2816 // the fall back entries have the lowest priority, by definition
2817 if ( fallback )
2818 {
2819 overwrite = false;
2820 }
2821 else
2822 {
2823 // have we seen this one before?
2824 int nIndex = m_aTypes.Index(data.type);
2825
2826 // and if we have, was it in this file? if not, we should
2827 // overwrite the previously seen one
2828 overwrite = nIndex == wxNOT_FOUND ||
2829 aIndicesSeenHere.Index(nIndex) == wxNOT_FOUND;
2830 }
2831
2832 wxLogTrace(TRACE_MIME, wxT("mailcap %s: %s [%s]"),
2833 data.type.c_str(), data.cmdOpen.c_str(),
2834 overwrite ? wxT("replace") : wxT("add"));
2835
2836 int n = AddToMimeData
2837 (
2838 data.type,
2839 data.icon,
2840 new wxMimeTypeCommands(data.verbs, data.commands),
2841 wxArrayString() /* extensions */,
2842 data.desc,
2843 overwrite
2844 );
2845
2846 if ( overwrite )
2847 {
2848 aIndicesSeenHere.Add(n);
2849 }
2850 }
2851
2852 return true;
2853 }
2854
2855 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
2856 {
2857 InitIfNeeded();
2858
2859 mimetypes.Empty();
2860
2861 wxString type;
2862 size_t count = m_aTypes.GetCount();
2863 for ( size_t n = 0; n < count; n++ )
2864 {
2865 // don't return template types from here (i.e. anything containg '*')
2866 type = m_aTypes[n];
2867 if ( type.Find(wxT('*')) == wxNOT_FOUND )
2868 {
2869 mimetypes.Add(type);
2870 }
2871 }
2872
2873 return mimetypes.GetCount();
2874 }
2875
2876 // ----------------------------------------------------------------------------
2877 // writing to MIME type files
2878 // ----------------------------------------------------------------------------
2879
2880 bool wxMimeTypesManagerImpl::Unassociate(wxFileType *ft)
2881 {
2882 wxArrayString sMimeTypes;
2883 ft->GetMimeTypes(sMimeTypes);
2884
2885 wxString sMime;
2886 size_t i;
2887 for (i = 0; i < sMimeTypes.GetCount(); i ++)
2888 {
2889 sMime = sMimeTypes.Item(i);
2890 int nIndex = m_aTypes.Index(sMime);
2891 if ( nIndex == wxNOT_FOUND)
2892 {
2893 // error if we get here ??
2894 return false;
2895 }
2896 else
2897 {
2898 WriteMimeInfo(nIndex, true);
2899 m_aTypes.RemoveAt(nIndex);
2900 m_aEntries.RemoveAt(nIndex);
2901 m_aExtensions.RemoveAt(nIndex);
2902 m_aDescriptions.RemoveAt(nIndex);
2903 m_aIcons.RemoveAt(nIndex);
2904 }
2905 }
2906 // check data integrity
2907 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
2908 m_aTypes.Count() == m_aExtensions.Count() &&
2909 m_aTypes.Count() == m_aIcons.Count() &&
2910 m_aTypes.Count() == m_aDescriptions.Count() );
2911
2912 return true;
2913 }
2914
2915 // ----------------------------------------------------------------------------
2916 // private functions
2917 // ----------------------------------------------------------------------------
2918
2919 static bool IsKnownUnimportantField(const wxString& fieldAll)
2920 {
2921 static const wxChar *knownFields[] =
2922 {
2923 wxT("x-mozilla-flags"),
2924 wxT("nametemplate"),
2925 wxT("textualnewlines"),
2926 };
2927
2928 wxString field = fieldAll.BeforeFirst(wxT('='));
2929 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
2930 {
2931 if ( field.CmpNoCase(knownFields[n]) == 0 )
2932 return true;
2933 }
2934
2935 return false;
2936 }
2937
2938 #endif
2939 // wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE
2940