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