]> git.saurik.com Git - wxWidgets.git/blame - src/unix/mimetype.cpp
Merge new item attributes if any are already existing
[wxWidgets.git] / src / unix / mimetype.cpp
CommitLineData
b13d92d1 1/////////////////////////////////////////////////////////////////////////////
7520f3da 2// Name: src/unix/mimetype.cpp
b13d92d1
VZ
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>
65571936 9// Licence: wxWindows licence (part of wxExtra library)
b13d92d1
VZ
10/////////////////////////////////////////////////////////////////////////////
11
2b813b73
VZ
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
678ebfcd
VZ
28// 9) KDE does not yet handle multiple actions; Netscape mode never will
29
7c2e5dec 30// TODO: this file is a mess, we need to split it and review everything (VZ)
f6bcfd97 31
b13d92d1
VZ
32// for compilers that support precompilation, includes "wx.h".
33#include "wx/wxprec.h"
34
35#ifdef __BORLANDC__
7520f3da 36 #pragma hdrstop
ce4169a4
RR
37#endif
38
b79e32cc 39#if wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE
ce4169a4 40
88a7a4e1
WS
41#include "wx/unix/mimetype.h"
42
ce4169a4 43#ifndef WX_PRECOMP
ad9835c9 44 #include "wx/dynarray.h"
7520f3da 45 #include "wx/string.h"
88a7a4e1 46 #include "wx/intl.h"
e4db172a 47 #include "wx/log.h"
de6185e2 48 #include "wx/utils.h"
7c2e5dec 49#endif
3d05544e 50
ce4169a4 51#include "wx/file.h"
2432b92d 52#include "wx/confbase.h"
b13d92d1 53
7dc3cc31
VS
54#include "wx/ffile.h"
55#include "wx/textfile.h"
56#include "wx/dir.h"
7dc3cc31 57#include "wx/tokenzr.h"
c41dbc20 58#include "wx/iconloc.h"
1d529ef7 59#include "wx/filename.h"
b13d92d1 60
a24217f0
VZ
61#if wxUSE_LIBGNOMEVFS
62 // Not GUI dependent
63 #include "wx/gtk/gnome/gvfs.h"
64#endif
2b850ae1 65
b13d92d1
VZ
66// other standard headers
67#include <ctype.h>
68
678ebfcd
VZ
69// this class extends wxTextFile
70//
71// VZ: ???
2b813b73
VZ
72class wxMimeTextFile : public wxTextFile
73{
74public:
75 // constructors
76 wxMimeTextFile () : wxTextFile () {};
7c2e5dec 77 wxMimeTextFile(const wxString& strFile) : wxTextFile(strFile) {};
2b813b73 78
d0ee33f5 79 int pIndexOf(const wxString & sSearch, bool bIncludeComments = false, int iStart = 0)
2b813b73
VZ
80 {
81 size_t i = iStart;
82 int nResult = wxNOT_FOUND;
dfea7acc
DS
83 if (i >= GetLineCount())
84 return wxNOT_FOUND;
2b813b73
VZ
85
86 wxString sTest = sSearch;
87 sTest.MakeLower();
88 wxString sLine;
89
90 if (bIncludeComments)
91 {
dfea7acc 92 while ( i < GetLineCount() )
2b813b73 93 {
dfea7acc 94 sLine = GetLine(i);
2b813b73 95 sLine.MakeLower();
dfea7acc
DS
96 if (sLine.Contains(sTest))
97 nResult = (int) i;
7c2e5dec 98
2b813b73
VZ
99 i++;
100 }
101 }
102 else
103 {
104 while ( (i < GetLineCount()) )
105 {
dfea7acc 106 sLine = GetLine(i);
2b813b73
VZ
107 sLine.MakeLower();
108 if ( ! sLine.StartsWith(wxT("#")))
109 {
dfea7acc
DS
110 if (sLine.Contains(sTest))
111 nResult = (int) i;
2b813b73 112 }
dfea7acc 113
2b813b73
VZ
114 i++;
115 }
116 }
dfea7acc 117
2b813b73
VZ
118 return nResult;
119 }
120
121 bool CommentLine(int nIndex)
122 {
dfea7acc
DS
123 if (nIndex < 0)
124 return false;
125 if (nIndex >= (int)GetLineCount() )
126 return false;
7c2e5dec 127
2b813b73 128 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
d0ee33f5 129 return true;
2b813b73
VZ
130 }
131
132 bool CommentLine(const wxString & sTest)
133 {
134 int nIndex = pIndexOf(sTest);
dfea7acc
DS
135 if (nIndex < 0)
136 return false;
137 if (nIndex >= (int)GetLineCount() )
138 return false;
139
2b813b73 140 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
d0ee33f5 141 return true;
2b813b73
VZ
142 }
143
dfea7acc 144 wxString GetVerb(size_t i)
2b813b73 145 {
dfea7acc
DS
146 if (i > GetLineCount() )
147 return wxEmptyString;
148
2b813b73
VZ
149 wxString sTmp = GetLine(i).BeforeFirst(wxT('='));
150 return sTmp;
151 }
152
dfea7acc 153 wxString GetCmd(size_t i)
2b813b73 154 {
dfea7acc
DS
155 if (i > GetLineCount() )
156 return wxEmptyString;
157
2b813b73
VZ
158 wxString sTmp = GetLine(i).AfterFirst(wxT('='));
159 return sTmp;
160 }
161};
162
b12915c1
VZ
163// in case we're compiling in non-GUI mode
164class WXDLLEXPORT wxIcon;
165
f6bcfd97
BP
166// ----------------------------------------------------------------------------
167// constants
168// ----------------------------------------------------------------------------
169
170// MIME code tracing mask
dfea7acc 171#define TRACE_MIME wxT("mime")
f6bcfd97 172
678ebfcd 173// give trace messages about the results of mailcap tests
dfea7acc 174#define TRACE_MIME_TEST wxT("mimetest")
678ebfcd 175
f6bcfd97
BP
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
183static bool IsKnownUnimportantField(const wxString& field);
184
b13d92d1
VZ
185// ----------------------------------------------------------------------------
186// private classes
187// ----------------------------------------------------------------------------
188
2b813b73 189
a6c65e88 190// This class uses both mailcap and mime.types to gather information about file
b13d92d1
VZ
191// types.
192//
a6c65e88
VZ
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.
b13d92d1
VZ
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
a6c65e88
VZ
222// * needsterminal means that a new interactive console must be created for
223// the viewer
b13d92d1
VZ
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//
a6c65e88 232// Parameter/filename expansion:
b13d92d1
VZ
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//
b13d92d1
VZ
241//
242// There are 2 possible formats for mime.types file, one entry per line (used
a6c65e88
VZ
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).
b13d92d1
VZ
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>
ec4768ef 251// b) for "expanded" format:
1457c903
VZ
252// type=<mime type> BACKSLASH
253// desc="<description>" BACKSLASH
a6c65e88 254// exts="<comma separated list of extensions>"
b13d92d1 255//
1457c903
VZ
256// (where BACKSLASH is a literal '\\' which we can't put here because cpp
257// misinterprets it)
258//
b13d92d1
VZ
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.
b9517a0a
VZ
265
266// ----------------------------------------------------------------------------
2b813b73 267// wxGNOME
b9517a0a
VZ
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//
afaee315
VZ
274// Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
275// just before the field name.
b9517a0a 276
b9517a0a 277
25d599ae
VS
278void wxMimeTypesManagerImpl::LoadGnomeDataFromKeyFile(const wxString& filename,
279 const wxArrayString& dirs)
4d2976ad 280{
2b813b73 281 wxTextFile textfile(filename);
2b5f62a0 282#if defined(__WXGTK20__) && wxUSE_UNICODE
8dee721e 283 if ( !textfile.Open(wxMBConvUTF8(wxMBConvUTF8::MAP_INVALID_UTF8_TO_OCTAL)) )
2b5f62a0 284#else
2b813b73 285 if ( !textfile.Open() )
2b5f62a0 286#endif
2b813b73 287 return;
dfea7acc 288
2b813b73 289 wxLogTrace(TRACE_MIME, wxT("--- Opened Gnome file %s ---"),
678ebfcd 290 filename.c_str());
4d2976ad 291
2b850ae1
RR
292 wxArrayString search_dirs( dirs );
293
2b813b73
VZ
294 // values for the entry being parsed
295 wxString curMimeType, curIconFile;
678ebfcd 296 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
4d2976ad 297
2b813b73
VZ
298 wxArrayString strExtensions;
299 wxString strDesc;
4d2976ad 300
2b813b73
VZ
301 const wxChar *pc;
302 size_t nLineCount = textfile.GetLineCount();
303 size_t nLine = 0;
7c2e5dec 304 while ( nLine < nLineCount )
678ebfcd 305 {
2b813b73 306 pc = textfile[nLine].c_str();
dfea7acc 307 if ( *pc != wxT('#') )
678ebfcd 308 {
4d2976ad 309
2b813b73 310 wxLogTrace(TRACE_MIME, wxT("--- Reading from Gnome file %s '%s' ---"),
7c2e5dec 311 filename.c_str(), pc);
7520f3da 312
2b850ae1
RR
313 // trim trailing space and tab
314 while ((*pc == wxT(' ')) || (*pc == wxT('\t')))
315 pc++;
4d2976ad 316
2b813b73 317 wxString sTmp(pc);
2b850ae1
RR
318 int equal_pos = sTmp.Find( wxT('=') );
319 if (equal_pos > 0)
2b813b73 320 {
2b850ae1
RR
321 wxString left_of_equal = sTmp.Left( equal_pos );
322 const wxChar *right_of_equal = pc;
7520f3da
WS
323 right_of_equal += equal_pos+1;
324
2b850ae1 325 if (left_of_equal == wxT("icon_filename"))
25d599ae 326 {
dfea7acc 327 // GNOME 2:
2b850ae1 328 curIconFile = right_of_equal;
7520f3da 329
2b850ae1
RR
330 wxFileName newFile( curIconFile );
331 if (newFile.IsRelative() || newFile.FileExists())
25d599ae 332 {
2b850ae1 333 size_t nDirs = search_dirs.GetCount();
7520f3da 334
25d599ae
VS
335 for (size_t nDir = 0; nDir < nDirs; nDir++)
336 {
2b850ae1 337 newFile.SetPath( search_dirs[nDir] );
10274336
RR
338 newFile.AppendDir( wxT("pixmaps") );
339 newFile.AppendDir( wxT("document-icons") );
340 newFile.SetExt( wxT("png") );
1d529ef7 341 if (newFile.FileExists())
2b850ae1 342 {
1d529ef7 343 curIconFile = newFile.GetFullPath();
2b850ae1
RR
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 }
25d599ae
VS
354 }
355 }
356 }
2b850ae1 357 else if (left_of_equal == wxT("open"))
678ebfcd 358 {
2b850ae1
RR
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") );
678ebfcd 389 entry->Add(sTmp);
2b813b73
VZ
390 }
391
392 } // emd of has an equals sign
393 else
678ebfcd 394 {
2b813b73
VZ
395 // not a comment and not an equals sign
396 if (sTmp.Contains(wxT('/')))
678ebfcd 397 {
2b813b73
VZ
398 // this is the start of the new mimetype
399 // overwrite any existing data
678ebfcd
VZ
400 if (! curMimeType.empty())
401 {
7c2e5dec 402 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc );
2b813b73
VZ
403
404 // now get ready for next bit
678ebfcd 405 entry = new wxMimeTypeCommands;
2b813b73 406 }
dfea7acc 407
678ebfcd
VZ
408 curMimeType = sTmp.BeforeFirst(wxT(':'));
409 }
410 }
2b813b73 411 } // end of not a comment
dfea7acc 412
678ebfcd 413 // ignore blank lines
7c2e5dec 414 nLine++;
2b813b73 415 } // end of while, save any data
d0ee33f5 416
32592f4a
VZ
417 if ( curMimeType.empty() )
418 delete entry;
419 else
dfea7acc 420 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc);
4d2976ad
VS
421}
422
2b813b73 423void wxMimeTypesManagerImpl::LoadGnomeMimeTypesFromMimeFile(const wxString& filename)
4d2976ad
VS
424{
425 wxTextFile textfile(filename);
426 if ( !textfile.Open() )
427 return;
2b6d8c00
VZ
428
429 wxLogTrace(TRACE_MIME,
430 wxT("--- Opened Gnome file %s ---"),
431 filename.c_str());
4d2976ad
VS
432
433 // values for the entry being parsed
434 wxString curMimeType, curExtList;
435
436 const wxChar *pc;
437 size_t nLineCount = textfile.GetLineCount();
1c4cd9e0 438 for ( size_t nLine = 0; /* nothing */; nLine++ )
4d2976ad
VS
439 {
440 if ( nLine < nLineCount )
441 {
442 pc = textfile[nLine].c_str();
2b5f62a0 443 if ( *pc == wxT('#') )
4d2976ad
VS
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 }
b9517a0a 454
4d2976ad
VS
455 if ( !pc || !*pc )
456 {
457 // end of the entry
d0ee33f5 458 if ( !curMimeType.empty() && !curExtList.empty() )
b9517a0a 459 {
2b6d8c00
VZ
460 wxLogTrace(TRACE_MIME,
461 wxT("--- At end of Gnome file finding mimetype %s ---"),
462 curMimeType.c_str());
d0ee33f5 463
2b813b73 464 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
4d2976ad 465 }
b9517a0a 466
4d2976ad
VS
467 if ( !pc )
468 {
7c2e5dec 469 // the end: this can only happen if nLine == nLineCount
b9517a0a
VZ
470 break;
471 }
4d2976ad
VS
472
473 curExtList.Empty();
474
475 continue;
476 }
477
478 // what do we have here?
2b5f62a0 479 if ( *pc == wxT('\t') )
4d2976ad
VS
480 {
481 // this is a field=value ling
482 pc++; // skip leading TAB
483
2b6d8c00
VZ
484 static const int lenField = 5; // strlen("ext: ")
485 if ( wxStrncmp(pc, wxT("ext: "), lenField) == 0 )
4d2976ad 486 {
2b6d8c00
VZ
487 // skip it and take everything left until the end of line
488 curExtList = pc + lenField;
4d2976ad
VS
489 }
490 //else: some other field, we don't care
491 }
492 else
493 {
494 // this is the start of the new section
2b6d8c00
VZ
495 wxLogTrace(TRACE_MIME,
496 wxT("--- In Gnome file finding mimetype %s ---"),
497 curMimeType.c_str());
2b813b73 498
678ebfcd
VZ
499 if (! curMimeType.empty())
500 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
2b813b73 501
4d2976ad
VS
502 curMimeType.Empty();
503
2b5f62a0 504 while ( *pc != wxT(':') && *pc != wxT('\0') )
4d2976ad
VS
505 {
506 curMimeType += *pc++;
507 }
b9517a0a
VZ
508 }
509 }
510}
511
4d2976ad 512
25d599ae
VS
513void wxMimeTypesManagerImpl::LoadGnomeMimeFilesFromDir(
514 const wxString& dirbase, const wxArrayString& dirs)
b9517a0a 515{
d0ee33f5 516 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
dfea7acc 517 wxT("base directory shouldn't end with a slash") );
b9517a0a
VZ
518
519 wxString dirname = dirbase;
2b5f62a0 520 dirname << wxT("/mime-info");
d0ee33f5 521
b9517a0a
VZ
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
2b5f62a0 530 dirname += wxT('/');
b9517a0a
VZ
531
532 wxString filename;
1d529ef7 533 bool cont;
dfea7acc
DS
534
535 cont = dir.GetFirst(&filename, wxT("*.mime"), wxDIR_FILES);
b9517a0a
VZ
536 while ( cont )
537 {
2b813b73 538 LoadGnomeMimeTypesFromMimeFile(dirname + filename);
b9517a0a
VZ
539
540 cont = dir.GetNext(&filename);
541 }
fb5ddb4c 542
dfea7acc 543 cont = dir.GetFirst(&filename, wxT("*.keys"), wxDIR_FILES);
2b813b73 544 while ( cont )
b9517a0a 545 {
25d599ae 546 LoadGnomeDataFromKeyFile(dirname + filename, dirs);
b9517a0a 547
2b813b73
VZ
548 cont = dir.GetNext(&filename);
549 }
b9517a0a 550
7c2e5dec 551 // FIXME: Hack alert: We scan all icons and deduce the
1d529ef7
RR
552 // mime-type from the file name.
553 dirname = dirbase;
554 dirname << wxT("/pixmaps/document-icons");
d0ee33f5 555
1d529ef7
RR
556 // these are always empty in this file
557 wxArrayString strExtensions;
558 wxString strDesc;
d0ee33f5 559
1d529ef7 560 if ( !wxDir::Exists(dirname) )
ca06ee0d 561 {
7c2e5dec 562 // Just test for default GPE dir also
ca06ee0d 563 dirname = wxT("/usr/share/gpe/pixmaps/default/filemanager/document-icons");
d0ee33f5 564
ca06ee0d
RR
565 if ( !wxDir::Exists(dirname) )
566 return;
567 }
4d2976ad 568
1d529ef7 569 wxDir dir2( dirname );
2b813b73 570
1d529ef7
RR
571 cont = dir2.GetFirst(&filename, wxT("gnome-*.png"), wxDIR_FILES);
572 while ( cont )
573 {
10274336
RR
574 wxString mimeType = filename;
575 mimeType.Remove( 0, 6 ); // remove "gnome-"
7c2e5dec 576 mimeType.Remove( mimeType.Len() - 4, 4 ); // remove ".png"
10274336
RR
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;
dfea7acc 584 AddToMimeData( mimeType, iconFile, NULL, strExtensions, strDesc, true );
10274336 585 }
1d529ef7
RR
586
587 cont = dir2.GetNext(&filename);
588 }
589}
2b813b73
VZ
590
591void wxMimeTypesManagerImpl::GetGnomeMimeInfo(const wxString& sExtraDir)
4d2976ad 592{
4d2976ad 593 wxArrayString dirs;
d0ee33f5 594
1c4cd9e0 595 wxString gnomedir = wxGetenv( wxT("GNOMEDIR") );
10274336
RR
596 if (!gnomedir.empty())
597 {
598 gnomedir << wxT("/share");
599 dirs.Add( gnomedir );
600 }
601
2b5f62a0
VZ
602 dirs.Add(wxT("/usr/share"));
603 dirs.Add(wxT("/usr/local/share"));
d0ee33f5 604
10274336
RR
605 gnomedir = wxGetHomeDir();
606 gnomedir << wxT("/.gnome");
4d2976ad 607 dirs.Add( gnomedir );
d0ee33f5 608
dfea7acc
DS
609 if (!sExtraDir.empty())
610 dirs.Add( sExtraDir );
4d2976ad
VS
611
612 size_t nDirs = dirs.GetCount();
613 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
614 {
25d599ae 615 LoadGnomeMimeFilesFromDir(dirs[nDir], dirs);
4d2976ad
VS
616 }
617}
618
b9517a0a 619// ----------------------------------------------------------------------------
678ebfcd 620// KDE
b9517a0a
VZ
621// ----------------------------------------------------------------------------
622
678ebfcd 623
b9517a0a
VZ
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//
509a6196 627// 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
b9517a0a
VZ
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
2b813b73
VZ
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
dfea7acc 637bool wxMimeTypesManagerImpl::CheckKDEDirsExist( const wxString &sOK, const wxString &sTest )
10274336 638{
678ebfcd 639 if (sTest.empty())
10274336 640 {
dfea7acc 641 return wxDir::Exists(sOK);
10274336 642 }
2b813b73 643 else
10274336
RR
644 {
645 wxString sStart = sOK + wxT("/") + sTest.BeforeFirst(wxT('/'));
dfea7acc
DS
646 if (!wxDir::Exists(sStart))
647 wxMkdir(sStart);
10274336
RR
648 wxString sEnd = sTest.AfterFirst(wxT('/'));
649 return CheckKDEDirsExist(sStart, sEnd);
2b813b73
VZ
650 }
651}
652
653bool wxMimeTypesManagerImpl::WriteKDEMimeFile(int index, bool delete_index)
654{
655 wxMimeTextFile appoutfile, mimeoutfile;
656 wxString sHome = wxGetHomeDir();
657 wxString sTmp = wxT(".kde/share/mimelnk/");
678ebfcd 658 wxString sMime = m_aTypes[index];
dfea7acc 659 CheckKDEDirsExist(sHome, sTmp + sMime.BeforeFirst(wxT('/')) );
2b813b73
VZ
660 sTmp = sHome + wxT('/') + sTmp + sMime + wxT(".kdelnk");
661
662 bool bTemp;
dfea7acc 663 bool bMimeExists = mimeoutfile.Open(sTmp);
2b813b73
VZ
664 if (!bMimeExists)
665 {
dfea7acc 666 bTemp = mimeoutfile.Create(sTmp);
678ebfcd 667 // some unknown error eg out of disk space
dfea7acc
DS
668 if (!bTemp)
669 return false;
2b813b73
VZ
670 }
671
672 sTmp = wxT(".kde/share/applnk/");
dfea7acc 673 CheckKDEDirsExist(sHome, sTmp + sMime.AfterFirst(wxT('/')) );
2b813b73
VZ
674 sTmp = sHome + wxT('/') + sTmp + sMime.AfterFirst(wxT('/')) + wxT(".kdelnk");
675
676 bool bAppExists;
dfea7acc 677 bAppExists = appoutfile.Open(sTmp);
2b813b73
VZ
678 if (!bAppExists)
679 {
dfea7acc 680 bTemp = appoutfile.Create(sTmp);
2b813b73 681 // some unknown error eg out of disk space
dfea7acc
DS
682 if (!bTemp)
683 return false;
2b813b73
VZ
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=") );
dfea7acc
DS
718 if (!delete_index)
719 mimeoutfile.AddLine(wxT("Icon=") + sTmp );
7c2e5dec 720 appoutfile.CommentLine(wxT("Icon=") );
dfea7acc
DS
721 if (!delete_index)
722 appoutfile.AddLine(wxT("Icon=") + sTmp );
2b813b73
VZ
723
724 sTmp = wxT(" ") + m_aExtensions[index];
725
dfea7acc 726 wxStringTokenizer tokenizer(sTmp, wxT(" "));
2b813b73
VZ
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;
678ebfcd 734 }
dfea7acc
DS
735
736 if (!delete_index)
737 mimeoutfile.AddLine(sTmp);
2b813b73 738
678ebfcd 739 wxMimeTypeCommands * entries = m_aEntries[index];
2b813b73 740 // if we don't find open just have an empty string ... FIX this
dfea7acc 741 sTmp = entries->GetCommandForVerb(wxT("open"));
2b813b73
VZ
742 sTmp.Replace( wxT("%s"), wxT("%f") );
743
744 mimeoutfile.CommentLine(wxT("DefaultApp=") );
dfea7acc
DS
745 if (!delete_index)
746 mimeoutfile.AddLine(wxT("DefaultApp=") + sTmp);
2b813b73
VZ
747
748 sTmp.Replace( wxT("%f"), wxT("") );
749 appoutfile.CommentLine(wxT("Exec="));
dfea7acc
DS
750 if (!delete_index)
751 appoutfile.AddLine(wxT("Exec=") + sTmp);
2b813b73
VZ
752
753 if (entries->GetCount() > 1)
678ebfcd
VZ
754 {
755 //other actions as well as open
678ebfcd 756 }
dfea7acc 757
d0ee33f5 758 bTemp = false;
dfea7acc
DS
759 if (mimeoutfile.Write())
760 bTemp = true;
761 mimeoutfile.Close();
762 if (appoutfile.Write())
763 bTemp = true;
764 appoutfile.Close();
2b813b73
VZ
765
766 return bTemp;
2b813b73
VZ
767}
768
769void wxMimeTypesManagerImpl::LoadKDELinksForMimeSubtype(const wxString& dirbase,
b9517a0a 770 const wxString& subdir,
cdf339c9
VS
771 const wxString& filename,
772 const wxArrayString& icondirs)
b9517a0a 773{
2b813b73 774 wxMimeTextFile file;
dfea7acc
DS
775 if ( !file.Open(dirbase + filename) )
776 return;
b9517a0a 777
25d599ae 778 wxLogTrace(TRACE_MIME, wxT("loading KDE file %s"),
7c2e5dec 779 (dirbase + filename).c_str());
d0ee33f5 780
678ebfcd 781 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
2b813b73
VZ
782 wxArrayString sExts;
783 wxString mimetype, mime_desc, strIcon;
784
2b5f62a0 785 int nIndex = file.pIndexOf( wxT("MimeType=") );
2b813b73 786 if (nIndex == wxNOT_FOUND)
678ebfcd
VZ
787 {
788 // construct mimetype from the directory name and the basename of the
789 // file (it always has .kdelnk extension)
2b5f62a0 790 mimetype << subdir << wxT('/') << filename.BeforeLast( wxT('.') );
678ebfcd 791 }
dfea7acc
DS
792 else
793 mimetype = file.GetCmd(nIndex);
b9517a0a 794
276c7463
VZ
795 // first find the description string: it is the value in either "Comment="
796 // line or "Comment[<locale_name>]=" one
2b813b73 797 nIndex = wxNOT_FOUND;
276c7463
VZ
798
799 wxString comment;
7c2e5dec 800
276c7463
VZ
801#if wxUSE_INTL
802 wxLocale *locale = wxGetLocale();
803 if ( locale )
804 {
805 // try "Comment[locale name]" first
dfea7acc 806 comment << wxT("Comment[") + locale->GetName() + wxT("]=");
2b813b73 807 nIndex = file.pIndexOf(comment);
276c7463 808 }
7c2e5dec 809#endif
cdf339c9 810
2b813b73 811 if ( nIndex == wxNOT_FOUND )
276c7463 812 {
dfea7acc 813 comment = wxT("Comment=");
2b813b73 814 nIndex = file.pIndexOf(comment);
276c7463
VZ
815 }
816
dfea7acc
DS
817 if ( nIndex != wxNOT_FOUND )
818 mime_desc = file.GetCmd(nIndex);
276c7463 819 //else: no description
cdf339c9 820
276c7463
VZ
821 // next find the extensions
822 wxString mime_extension;
823
dfea7acc 824 nIndex = file.pIndexOf(wxT("Patterns="));
2b813b73 825 if ( nIndex != wxNOT_FOUND )
678ebfcd 826 {
dfea7acc 827 wxString exts = file.GetCmd(nIndex);
5bd3a2da 828
dfea7acc 829 wxStringTokenizer tokenizer(exts, wxT(";"));
276c7463 830 while ( tokenizer.HasMoreTokens() )
cdf339c9 831 {
276c7463 832 wxString e = tokenizer.GetNextToken();
7c2e5dec
DS
833
834 // don't support too difficult patterns
dfea7acc 835 if ( e.Left(2) != wxT("*.") )
7c2e5dec 836 continue;
276c7463
VZ
837
838 if ( !mime_extension.empty() )
839 {
840 // separate from the previous ext
dfea7acc 841 mime_extension << wxT(' ');
276c7463
VZ
842 }
843
cdf339c9 844 mime_extension << e.Mid(2);
cdf339c9 845 }
cdf339c9 846 }
dfea7acc 847
2b813b73 848 sExts.Add(mime_extension);
5bd3a2da 849
cdf339c9
VS
850 // ok, now we can take care of icon:
851
dfea7acc 852 nIndex = file.pIndexOf(wxT("Icon="));
2b813b73 853 if ( nIndex != wxNOT_FOUND )
b9517a0a 854 {
2b813b73 855 strIcon = file.GetCmd(nIndex);
7c2e5dec 856
25d599ae 857 wxLogTrace(TRACE_MIME, wxT(" icon %s"), strIcon.c_str());
d0ee33f5 858
7c2e5dec 859 // it could be the real path, but more often a short name
2b813b73 860 if (!wxFileExists(strIcon))
678ebfcd 861 {
2b813b73 862 // icon is just the short name
678ebfcd 863 if ( !strIcon.empty() )
cdf339c9 864 {
678ebfcd
VZ
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++ )
10274336 869 {
3d780434
RR
870 wxFileName fnameIcon( strIcon );
871 wxFileName fname( icondirs[nDir], fnameIcon.GetName() );
10274336 872 fname.SetExt( wxT("png") );
1d529ef7 873 if (fname.FileExists())
678ebfcd 874 {
1d529ef7 875 strIcon = fname.GetFullPath();
25d599ae 876 wxLogTrace(TRACE_MIME, wxT(" iconfile %s"), strIcon.c_str());
678ebfcd
VZ
877 break;
878 }
10274336 879 }
2b813b73 880 }
678ebfcd
VZ
881 }
882 }
dfea7acc 883
2b813b73
VZ
884 // now look for lines which know about the application
885 // exec= or DefaultApp=
886
887 nIndex = file.pIndexOf(wxT("DefaultApp"));
b9517a0a 888
2b813b73 889 if ( nIndex == wxNOT_FOUND )
678ebfcd 890 {
2b813b73
VZ
891 // no entry try exec
892 nIndex = file.pIndexOf(wxT("Exec"));
678ebfcd 893 }
2b813b73
VZ
894
895 if ( nIndex != wxNOT_FOUND )
678ebfcd 896 {
2b813b73 897 // we expect %f; others including %F and %U and %u are possible
7c2e5dec 898 wxString sTmp = file.GetCmd(nIndex);
dfea7acc 899 if (0 == sTmp.Replace( wxT("%f"), wxT("%s") ))
678ebfcd 900 sTmp = sTmp + wxT(" %s");
dfea7acc 901 entry->AddOrReplaceVerb(wxString(wxT("open")), sTmp );
b9517a0a 902 }
2b813b73 903
dfea7acc 904 AddToMimeData(mimetype, strIcon, entry, sExts, mime_desc);
b9517a0a
VZ
905}
906
2b813b73 907void wxMimeTypesManagerImpl::LoadKDELinksForMimeType(const wxString& dirbase,
cdf339c9
VS
908 const wxString& subdir,
909 const wxArrayString& icondirs)
b9517a0a
VZ
910{
911 wxString dirname = dirbase;
912 dirname += subdir;
913 wxDir dir(dirname);
914 if ( !dir.IsOpened() )
915 return;
916
25d599ae
VS
917 wxLogTrace(TRACE_MIME, wxT("--- Loading from KDE directory %s ---"),
918 dirname.c_str());
d0ee33f5 919
dfea7acc 920 dirname += wxT('/');
b9517a0a
VZ
921
922 wxString filename;
dfea7acc 923 bool cont = dir.GetFirst(&filename, wxT("*.kdelnk"), wxDIR_FILES);
b9517a0a
VZ
924 while ( cont )
925 {
2b813b73
VZ
926 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
927
928 cont = dir.GetNext(&filename);
929 }
dfea7acc 930
2b813b73 931 // new standard for Gnome and KDE
dfea7acc 932 cont = dir.GetFirst(&filename, wxT("*.desktop"), wxDIR_FILES);
2b813b73
VZ
933 while ( cont )
934 {
935 LoadKDELinksForMimeSubtype(dirname, subdir, filename, icondirs);
b9517a0a
VZ
936
937 cont = dir.GetNext(&filename);
938 }
939}
940
2b813b73 941void wxMimeTypesManagerImpl::LoadKDELinkFilesFromDir(const wxString& dirbase,
cdf339c9 942 const wxArrayString& icondirs)
b9517a0a 943{
d0ee33f5 944 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
dfea7acc 945 wxT("base directory shouldn't end with a slash") );
b9517a0a
VZ
946
947 wxString dirname = dirbase;
dfea7acc 948 dirname << wxT("/mimelnk");
b9517a0a
VZ
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
dfea7acc 958 dirname += wxT('/');
b9517a0a
VZ
959
960 wxString subdir;
961 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
962 while ( cont )
963 {
2b813b73 964 LoadKDELinksForMimeType(dirname, subdir, icondirs);
b9517a0a
VZ
965
966 cont = dir.GetNext(&subdir);
967 }
968}
969
2b813b73 970void wxMimeTypesManagerImpl::GetKDEMimeInfo(const wxString& sExtraDir)
b9517a0a
VZ
971{
972 wxArrayString dirs;
cdf339c9 973 wxArrayString icondirs;
320c341a
VS
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)
d0ee33f5 987
10274336
RR
988 // the variable $KDEDIR is set when KDE is running
989 wxString kdedir = wxGetenv( wxT("KDEDIR") );
d0ee33f5 990
10274336 991 if (!kdedir.empty())
1d529ef7 992 {
10274336
RR
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") );
d0ee33f5 999
320c341a
VS
1000 wxTextFile config;
1001 if (configFile.FileExists() && config.Open(configFile.GetFullPath()))
10274336 1002 {
10274336 1003 // $(KDEDIR)/share/config -> $(KDEDIR)/share
7c2e5dec 1004 configFile.RemoveDir( configFile.GetDirCount() - 1 );
10274336
RR
1005 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1006 configFile.AppendDir( wxT("icons") );
1007
1008 // Check for entry
320c341a
VS
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 }
dfea7acc 1016
320c341a 1017 configFile.AppendDir(theme);
10274336
RR
1018 }
1019 else
1020 {
1021 // $(KDEDIR)/share/config -> $(KDEDIR)/share
dfea7acc
DS
1022 configFile.RemoveDir( configFile.GetDirCount() - 1 );
1023
10274336
RR
1024 // $(KDEDIR)/share/ -> $(KDEDIR)/share/icons
1025 configFile.AppendDir( wxT("icons") );
dfea7acc 1026
10274336
RR
1027 // $(KDEDIR)/share/icons -> $(KDEDIR)/share/icons/default.kde
1028 configFile.AppendDir( wxT("default.kde") );
1029 }
d0ee33f5 1030
10274336 1031 configFile.SetName( wxEmptyString );
16c587ca
RR
1032 configFile.AppendDir( wxT("32x32") );
1033 configFile.AppendDir( wxT("mimetypes") );
d0ee33f5 1034
10274336 1035 // Just try a few likely icons theme names
d0ee33f5 1036
7c2e5dec 1037 int pos = configFile.GetDirCount() - 3;
d0ee33f5 1038
10274336
RR
1039 if (!wxDir::Exists(configFile.GetPath()))
1040 {
16c587ca
RR
1041 configFile.RemoveDir( pos );
1042 configFile.InsertDir( pos, wxT("default.kde") );
10274336 1043 }
d0ee33f5 1044
10274336
RR
1045 if (!wxDir::Exists(configFile.GetPath()))
1046 {
16c587ca
RR
1047 configFile.RemoveDir( pos );
1048 configFile.InsertDir( pos, wxT("default") );
10274336 1049 }
d0ee33f5 1050
10274336
RR
1051 if (!wxDir::Exists(configFile.GetPath()))
1052 {
16c587ca
RR
1053 configFile.RemoveDir( pos );
1054 configFile.InsertDir( pos, wxT("crystalsvg") );
10274336 1055 }
d0ee33f5 1056
10274336
RR
1057 if (!wxDir::Exists(configFile.GetPath()))
1058 {
16c587ca
RR
1059 configFile.RemoveDir( pos );
1060 configFile.InsertDir( pos, wxT("crystal") );
10274336 1061 }
d0ee33f5 1062
10274336 1063 if (wxDir::Exists(configFile.GetPath()))
10274336 1064 icondirs.Add( configFile.GetFullPath() );
1d529ef7 1065 }
cdf339c9
VS
1066
1067 // settings in ~/.kde have maximal priority
2b5f62a0
VZ
1068 dirs.Add(wxGetHomeDir() + wxT("/.kde/share"));
1069 icondirs.Add(wxGetHomeDir() + wxT("/.kde/share/icons/"));
509a6196 1070
10274336 1071 if (kdedir)
509a6196 1072 {
2b5f62a0
VZ
1073 dirs.Add( wxString(kdedir) + wxT("/share") );
1074 icondirs.Add( wxString(kdedir) + wxT("/share/icons/") );
509a6196
VZ
1075 }
1076 else
1077 {
1078 // try to guess KDEDIR
dfea7acc
DS
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/"));
509a6196
VZ
1084 }
1085
dfea7acc
DS
1086 if (!sExtraDir.empty())
1087 dirs.Add(sExtraDir);
2b813b73
VZ
1088 icondirs.Add(sExtraDir + wxT("/icons"));
1089
b9517a0a
VZ
1090 size_t nDirs = dirs.GetCount();
1091 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1092 {
2b813b73 1093 LoadKDELinkFilesFromDir(dirs[nDir], icondirs);
b9517a0a 1094 }
b9517a0a
VZ
1095}
1096
2b813b73
VZ
1097// ----------------------------------------------------------------------------
1098// wxFileTypeImpl (Unix)
1099// ----------------------------------------------------------------------------
1100
2b813b73 1101wxString wxFileTypeImpl::GetExpandedCommand(const wxString & verb, const wxFileType::MessageParameters& params) const
b9517a0a 1102{
2b813b73
VZ
1103 wxString sTmp;
1104 size_t i = 0;
678ebfcd 1105 while ( (i < m_index.GetCount() ) && sTmp.empty() )
b9517a0a 1106 {
dfea7acc
DS
1107 sTmp = m_manager->GetCommand( verb, m_index[i] );
1108 i++;
b9517a0a
VZ
1109 }
1110
2b813b73
VZ
1111 return wxFileType::ExpandCommand(sTmp, params);
1112}
b9517a0a 1113
da0766ab 1114bool wxFileTypeImpl::GetIcon(wxIconLocation *iconLoc) const
2b813b73
VZ
1115{
1116 wxString sTmp;
1117 size_t i = 0;
678ebfcd 1118 while ( (i < m_index.GetCount() ) && sTmp.empty() )
c786f763
VZ
1119 {
1120 sTmp = m_manager->m_aIcons[m_index[i]];
dfea7acc 1121 i++;
c786f763 1122 }
dfea7acc
DS
1123
1124 if ( sTmp.empty() )
d0ee33f5 1125 return false;
b9517a0a 1126
da0766ab 1127 if ( iconLoc )
c786f763 1128 {
a49686b4 1129 iconLoc->SetFileName(sTmp);
2b813b73 1130 }
c786f763 1131
d0ee33f5 1132 return true;
c786f763 1133}
2b813b73 1134
dfea7acc 1135bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
2b813b73
VZ
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]]);
dfea7acc 1140
d0ee33f5 1141 return true;
2b813b73
VZ
1142}
1143
2b813b73
VZ
1144size_t wxFileTypeImpl::GetAllCommands(wxArrayString *verbs,
1145 wxArrayString *commands,
1146 const wxFileType::MessageParameters& params) const
1147{
2b813b73
VZ
1148 wxString vrb, cmd, sTmp;
1149 size_t count = 0;
678ebfcd 1150 wxMimeTypeCommands * sPairs;
2b813b73
VZ
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
7c2e5dec 1154 for (size_t n = 0; ((count == 0) && (n < m_index.GetCount())); n++)
2b813b73
VZ
1155 {
1156 // list of verb = command pairs for this mimetype
1157 sPairs = m_manager->m_aEntries [m_index[n]];
1158 size_t i;
dfea7acc
DS
1159 for ( i = 0; i < sPairs->GetCount(); i++ )
1160 {
1161 vrb = sPairs->GetVerb(i);
7c2e5dec 1162 // some gnome entries have "." inside
dfea7acc
DS
1163 vrb = vrb.AfterLast(wxT('.'));
1164 cmd = sPairs->GetCmd(i);
1165 if (! cmd.empty() )
2b813b73 1166 {
dfea7acc 1167 cmd = wxFileType::ExpandCommand(cmd, params);
7c2e5dec 1168 count++;
dfea7acc
DS
1169 if ( vrb.IsSameAs(wxT("open")))
1170 {
d6a7ca31
VZ
1171 if ( verbs )
1172 verbs->Insert(vrb, 0u);
1173 if ( commands )
1174 commands ->Insert(cmd, 0u);
dfea7acc
DS
1175 }
1176 else
1177 {
d6a7ca31
VZ
1178 if ( verbs )
1179 verbs->Add(vrb);
1180 if ( commands )
1181 commands->Add(cmd);
dfea7acc
DS
1182 }
1183 }
2b813b73 1184 }
2b813b73 1185 }
2b813b73 1186
dfea7acc 1187 return count;
2b813b73
VZ
1188}
1189
1190bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
1191{
1192 wxString strExtensions = m_manager->GetExtension(m_index[0]);
1193 extensions.Empty();
1194
1c4cd9e0 1195 // one extension in the space or comma-delimited list
2b813b73 1196 wxString strExt;
dfea7acc
DS
1197 for ( const wxChar *p = strExtensions; /* nothing */; p++ )
1198 {
1199 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') )
1200 {
1201 if ( !strExt.empty() )
1202 {
2b813b73
VZ
1203 extensions.Add(strExt);
1204 strExt.Empty();
1205 }
7c2e5dec
DS
1206 //else: repeated spaces
1207 // (shouldn't happen, but it's not that important if it does happen)
2b813b73
VZ
1208
1209 if ( *p == wxT('\0') )
1210 break;
1211 }
dfea7acc
DS
1212 else if ( *p == wxT('.') )
1213 {
2b813b73 1214 // remove the dot from extension (but only if it's the first char)
dfea7acc
DS
1215 if ( !strExt.empty() )
1216 {
2b813b73
VZ
1217 strExt += wxT('.');
1218 }
1219 //else: no, don't append it
1220 }
dfea7acc
DS
1221 else
1222 {
2b813b73
VZ
1223 strExt += *p;
1224 }
1225 }
1226
d0ee33f5 1227 return true;
2b813b73
VZ
1228}
1229
7c2e5dec 1230// set an arbitrary command:
2b813b73 1231// could adjust the code to ask confirmation if it already exists and
d0ee33f5 1232// overwriteprompt is true, but this is currently ignored as *Associate* has
2b813b73 1233// no overwrite prompt
17a1ebd1
VZ
1234bool
1235wxFileTypeImpl::SetCommand(const wxString& cmd,
1236 const wxString& verb,
1237 bool WXUNUSED(overwriteprompt))
d84afea9 1238{
2b813b73 1239 wxArrayString strExtensions;
678ebfcd 1240 wxString strDesc, strIcon;
2b813b73 1241
2b813b73 1242 wxArrayString strTypes;
dfea7acc 1243 GetMimeTypes(strTypes);
926ce9e3 1244 if ( strTypes.IsEmpty() )
dfea7acc 1245 return false;
2b813b73 1246
926ce9e3
VZ
1247 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1248 entry->Add(verb + wxT("=") + cmd + wxT(" %s "));
1249
dfea7acc 1250 bool ok = true;
926ce9e3 1251 for ( size_t i = 0; i < strTypes.GetCount(); i++ )
d84afea9 1252 {
dfea7acc
DS
1253 if (!m_manager->DoAssociation(strTypes[i], strIcon, entry, strExtensions, strDesc))
1254 ok = false;
d84afea9 1255 }
2b813b73 1256
dfea7acc 1257 return ok;
d84afea9 1258}
2b813b73
VZ
1259
1260// ignore index on the grouds that we only have one icon in a Unix file
17a1ebd1 1261bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int WXUNUSED(index))
d84afea9 1262{
dfea7acc
DS
1263 if (strIcon.empty())
1264 return false;
1265
2b813b73
VZ
1266 wxArrayString strExtensions;
1267 wxString strDesc;
1268
2b813b73 1269 wxArrayString strTypes;
dfea7acc 1270 GetMimeTypes(strTypes);
cb0b7b7d 1271 if ( strTypes.IsEmpty() )
dfea7acc 1272 return false;
2b813b73 1273
cb0b7b7d 1274 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
dfea7acc 1275 bool ok = true;
cb0b7b7d 1276 for ( size_t i = 0; i < strTypes.GetCount(); i++ )
d84afea9 1277 {
cb0b7b7d
VZ
1278 if ( !m_manager->DoAssociation
1279 (
1280 strTypes[i],
1281 strIcon,
1282 entry,
1283 strExtensions,
1284 strDesc
1285 ) )
1286 {
dfea7acc 1287 ok = false;
cb0b7b7d 1288 }
d84afea9 1289 }
2b813b73 1290
dfea7acc 1291 return ok;
d84afea9
GD
1292}
1293
2b813b73
VZ
1294// ----------------------------------------------------------------------------
1295// wxMimeTypesManagerImpl (Unix)
1296// ----------------------------------------------------------------------------
1297
2b813b73
VZ
1298wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1299{
d0ee33f5 1300 m_initialized = false;
2b813b73
VZ
1301 m_mailcapStylesInited = 0;
1302}
1303
1d529ef7
RR
1304void wxMimeTypesManagerImpl::InitIfNeeded()
1305{
1306 if ( !m_initialized )
1307 {
1308 // set the flag first to prevent recursion
d0ee33f5 1309 m_initialized = true;
7520f3da 1310
10274336 1311 wxString wm = wxGetenv( wxT("WINDOWMANAGER") );
d0ee33f5 1312
10274336 1313 if (wm.Find( wxT("kde") ) != wxNOT_FOUND)
2b850ae1 1314 Initialize( wxMAILCAP_KDE );
10274336 1315 else if (wm.Find( wxT("gnome") ) != wxNOT_FOUND)
2b850ae1 1316 Initialize( wxMAILCAP_GNOME );
10274336 1317 else
dfea7acc 1318 Initialize();
1d529ef7
RR
1319 }
1320}
1321
2b813b73
VZ
1322// read system and user mailcaps and other files
1323void wxMimeTypesManagerImpl::Initialize(int mailcapStyles,
1324 const wxString& sExtraDir)
1325{
1326 // read mimecap amd mime.types
a06c1b9b
VZ
1327 if ( (mailcapStyles & wxMAILCAP_NETSCAPE) ||
1328 (mailcapStyles & wxMAILCAP_STANDARD) )
2b813b73
VZ
1329 GetMimeInfo(sExtraDir);
1330
1331 // read GNOME tables
1d529ef7 1332 if (mailcapStyles & wxMAILCAP_GNOME)
2b813b73
VZ
1333 GetGnomeMimeInfo(sExtraDir);
1334
1335 // read KDE tables
1d529ef7 1336 if (mailcapStyles & wxMAILCAP_KDE)
2b813b73
VZ
1337 GetKDEMimeInfo(sExtraDir);
1338
1339 m_mailcapStylesInited |= mailcapStyles;
1340}
1341
1342// clear data so you can read another group of WM files
1343void wxMimeTypesManagerImpl::ClearData()
1344{
dfea7acc
DS
1345 m_aTypes.Clear();
1346 m_aIcons.Clear();
1347 m_aExtensions.Clear();
1348 m_aDescriptions.Clear();
2b813b73 1349
2b5f62a0 1350 WX_CLEAR_ARRAY(m_aEntries);
678ebfcd
VZ
1351 m_aEntries.Empty();
1352
2b813b73
VZ
1353 m_mailcapStylesInited = 0;
1354}
1355
1356wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1357{
678ebfcd 1358 ClearData();
2b813b73
VZ
1359}
1360
dfea7acc 1361void wxMimeTypesManagerImpl::GetMimeInfo(const wxString& sExtraDir)
2b813b73
VZ
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;
dfea7acc
DS
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("/") );
2b813b73
VZ
1387
1388 size_t nDirs = dirs.GetCount();
1389 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1390 {
1391 wxString file = dirs[nDir] + wxT("mailcap");
dfea7acc
DS
1392 if ( wxFile::Exists(file) )
1393 {
2b813b73
VZ
1394 ReadMailcap(file);
1395 }
1396
1397 file = dirs[nDir] + wxT("mime.types");
dfea7acc
DS
1398 if ( wxFile::Exists(file) )
1399 {
2b813b73
VZ
1400 ReadMimeTypes(file);
1401 }
1402 }
2b813b73
VZ
1403}
1404
dfea7acc 1405bool wxMimeTypesManagerImpl::WriteToMimeTypes(int index, bool delete_index)
2b813b73
VZ
1406{
1407 // check we have the right manager
a06c1b9b 1408 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD) )
d0ee33f5 1409 return false;
2b813b73
VZ
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 {
7c2e5dec 1420 bTemp = file.Open(strUserMailcap);
2b813b73
VZ
1421 }
1422 else
1423 {
dfea7acc
DS
1424 if (delete_index)
1425 return false;
1426
2b813b73
VZ
1427 bTemp = file.Create(strUserMailcap);
1428 }
dfea7acc 1429
2b813b73
VZ
1430 if (bTemp)
1431 {
1432 int nIndex;
d0ee33f5 1433 // test for netscape's header and return false if its found
dfea7acc 1434 nIndex = file.pIndexOf(wxT("#--Netscape"));
2b813b73
VZ
1435 if (nIndex != wxNOT_FOUND)
1436 {
4362c705 1437 wxFAIL_MSG(wxT("Error in .mime.types\nTrying to mix Netscape and Metamail formats\nFile not modified"));
d0ee33f5 1438 return false;
2b813b73 1439 }
dfea7acc 1440
2b813b73
VZ
1441 // write it in alternative format
1442 // get rid of unwanted entries
1443 wxString strType = m_aTypes[index];
dfea7acc
DS
1444 nIndex = file.pIndexOf(strType);
1445
2b813b73 1446 // get rid of all the unwanted entries...
dfea7acc
DS
1447 if (nIndex != wxNOT_FOUND)
1448 file.CommentLine(nIndex);
2b813b73
VZ
1449
1450 if (!delete_index)
1451 {
1452 // add the new entries in
7c2e5dec 1453 wxString sTmp = strType.Append( wxT(' '), 40 - strType.Len() );
2b813b73 1454 sTmp = sTmp + m_aExtensions[index];
dfea7acc 1455 file.AddLine(sTmp);
2b813b73
VZ
1456 }
1457
dfea7acc
DS
1458 bTemp = file.Write();
1459 file.Close();
2b813b73 1460 }
dfea7acc 1461
2b813b73
VZ
1462 return bTemp;
1463}
1464
dfea7acc 1465bool wxMimeTypesManagerImpl::WriteToNSMimeTypes(int index, bool delete_index)
2b813b73
VZ
1466{
1467 //check we have the right managers
1468 if (! ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) )
d0ee33f5 1469 return false;
2b813b73
VZ
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 {
7c2e5dec 1480 bTemp = file.Open(strUserMailcap);
2b813b73
VZ
1481 }
1482 else
1483 {
dfea7acc
DS
1484 if (delete_index)
1485 return false;
1486
2b813b73
VZ
1487 bTemp = file.Create(strUserMailcap);
1488 }
dfea7acc 1489
2b813b73
VZ
1490 if (bTemp)
1491 {
2b813b73
VZ
1492 // write it in the format that Netscape uses
1493 int nIndex;
1494 // test for netscape's header and insert if required...
d0ee33f5 1495 // this is a comment so use true
dfea7acc 1496 nIndex = file.pIndexOf(wxT("#--Netscape"), true);
2b813b73
VZ
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
dfea7acc 1502 if (file.GetLineCount() > 0)
2b813b73 1503 {
4362c705 1504 wxFAIL_MSG(wxT(".mime.types File not in Netscape format\nNo entries written to\n.mime.types or to .mailcap"));
d0ee33f5 1505 return false;
2b813b73 1506 }
dfea7acc
DS
1507
1508 file.InsertLine(wxT( "#--Netscape Communications Corporation MIME Information" ), 0);
2b813b73
VZ
1509 nIndex = 0;
1510 }
1511
1512 wxString strType = wxT("type=") + m_aTypes[index];
dfea7acc
DS
1513 nIndex = file.pIndexOf(strType);
1514
2b813b73
VZ
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];
dfea7acc 1523
2b813b73 1524 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mime.types line '%d %s' ---"), nIndex, sOld.c_str());
dfea7acc
DS
1525
1526 nIndex++;
2b813b73 1527 }
dfea7acc
DS
1528
1529 if (nIndex < (int) file.GetLineCount())
1530 file.CommentLine(nIndex);
2b813b73 1531 }
dfea7acc
DS
1532 else
1533 nIndex = (int) file.GetLineCount();
2b813b73
VZ
1534
1535 wxString sTmp = strType + wxT(" \\");
dfea7acc
DS
1536 if (!delete_index)
1537 file.InsertLine(sTmp, nIndex);
1538
678ebfcd 1539 if ( ! m_aDescriptions.Item(index).empty() )
2b813b73 1540 {
dfea7acc 1541 sTmp = wxT("desc=\"") + m_aDescriptions[index]+ wxT("\" \\"); //.trim ??
2b813b73
VZ
1542 if (!delete_index)
1543 {
7c2e5dec 1544 nIndex++;
dfea7acc 1545 file.InsertLine(sTmp, nIndex);
2b813b73
VZ
1546 }
1547 }
dfea7acc 1548
7c2e5dec 1549 wxString sExts = m_aExtensions.Item(index);
dfea7acc 1550 sTmp = wxT("exts=\"") + sExts.Trim(false).Trim() + wxT("\"");
2b813b73
VZ
1551 if (!delete_index)
1552 {
7c2e5dec 1553 nIndex++;
dfea7acc 1554 file.InsertLine(sTmp, nIndex);
2b813b73
VZ
1555 }
1556
dfea7acc
DS
1557 bTemp = file.Write();
1558 file.Close();
2b813b73 1559 }
dfea7acc 1560
2b813b73
VZ
1561 return bTemp;
1562}
1563
dfea7acc 1564bool wxMimeTypesManagerImpl::WriteToMailCap(int index, bool delete_index)
2b813b73
VZ
1565{
1566 //check we have the right managers
1567 if ( !( ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) ||
a06c1b9b 1568 ( m_mailcapStylesInited & wxMAILCAP_STANDARD) ) )
d0ee33f5 1569 return false;
2b813b73 1570
7c2e5dec 1571 bool bTemp = false;
2b813b73
VZ
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 {
7c2e5dec 1580 bTemp = file.Open(strUserMailcap);
2b813b73 1581 }
be0a33fb 1582 else
2b813b73 1583 {
dfea7acc
DS
1584 if (delete_index)
1585 return false;
1586
2b813b73
VZ
1587 bTemp = file.Create(strUserMailcap);
1588 }
dfea7acc 1589
2b813b73
VZ
1590 if (bTemp)
1591 {
1592 // now got a file we can write to ....
678ebfcd
VZ
1593 wxMimeTypeCommands * entries = m_aEntries[index];
1594 size_t iOpen;
dfea7acc 1595 wxString sCmd = entries->GetCommandForVerb(wxT("open"), &iOpen);
2b813b73
VZ
1596 wxString sTmp;
1597
1598 sTmp = m_aTypes[index];
1599 wxString sOld;
1600 int nIndex = file.pIndexOf(sTmp);
7c2e5dec 1601
2b813b73
VZ
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);
6dc6fda6 1611
2b813b73
VZ
1612 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1613 {
1614 file.CommentLine(nIndex);
dfea7acc
DS
1615 if (nIndex < (int) file.GetLineCount())
1616 sOld = sOld + file[nIndex];
2b813b73 1617 }
7c2e5dec 1618
dfea7acc
DS
1619 if (nIndex < (int)
1620 file.GetLineCount()) file.CommentLine(nIndex);
2b813b73 1621 }
6dc6fda6 1622
678ebfcd 1623 sTmp = sTmp + wxT(";") + sCmd; //includes wxT(" %s ");
b9517a0a 1624
2b813b73 1625 // write it in the format that Netscape uses (default)
7c2e5dec 1626 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD ) )
2b813b73 1627 {
dfea7acc
DS
1628 if (! delete_index)
1629 file.InsertLine(sTmp, nIndex);
1630 nIndex++;
2b813b73 1631 }
2b813b73
VZ
1632 else
1633 {
dfea7acc
DS
1634 // write extended format
1635
1636 // TODO - FIX this code:
2b813b73
VZ
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
dfea7acc
DS
1642 wxStringTokenizer sT(sOld, wxT(";\\"));
1643 if (sT.CountTokens() > 2)
2b813b73
VZ
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();
678ebfcd 1652 while ( ! s.empty() )
2b813b73 1653 {
d0ee33f5 1654 bool bKnownToken = false;
dfea7acc
DS
1655 if (s.Contains(wxT("description=")))
1656 bKnownToken = true;
1657 if (s.Contains(wxT("x11-bitmap=")))
1658 bKnownToken = true;
7c2e5dec 1659
2b813b73
VZ
1660 size_t i;
1661 for (i=0; i < entries->GetCount(); i++)
1662 {
dfea7acc
DS
1663 if (s.Contains(entries->GetVerb(i)))
1664 bKnownToken = true;
2b813b73 1665 }
dfea7acc 1666
2b813b73
VZ
1667 if (!bKnownToken)
1668 {
1669 sTmp = sTmp + wxT("; \\");
dfea7acc 1670 file.InsertLine(sTmp, nIndex);
2b813b73
VZ
1671 sTmp = s;
1672 }
cdf339c9 1673
dfea7acc
DS
1674 s = sT.GetNextToken();
1675 }
2b813b73 1676 }
5bd3a2da 1677
678ebfcd 1678 if (! m_aDescriptions[index].empty() )
2b813b73
VZ
1679 {
1680 sTmp = sTmp + wxT("; \\");
dfea7acc 1681 file.InsertLine(sTmp, nIndex);
7c2e5dec 1682 nIndex++;
2b813b73
VZ
1683 sTmp = wxT(" description=\"") + m_aDescriptions[index] + wxT("\"");
1684 }
cdf339c9 1685
678ebfcd 1686 if (! m_aIcons[index].empty() )
2b813b73
VZ
1687 {
1688 sTmp = sTmp + wxT("; \\");
dfea7acc 1689 file.InsertLine(sTmp, nIndex);
7c2e5dec 1690 nIndex++;
2b813b73
VZ
1691 sTmp = wxT(" x11-bitmap=\"") + m_aIcons[index] + wxT("\"");
1692 }
cdf339c9 1693
dfea7acc 1694 if ( entries->GetCount() > 1 )
2b813b73
VZ
1695 {
1696 size_t i;
1697 for (i=0; i < entries->GetCount(); i++)
1698 if ( i != iOpen )
1699 {
1700 sTmp = sTmp + wxT("; \\");
dfea7acc 1701 file.InsertLine(sTmp, nIndex);
7c2e5dec 1702 nIndex++;
678ebfcd 1703 sTmp = wxT(" ") + entries->GetVerbCmd(i);
2b813b73
VZ
1704 }
1705 }
b9517a0a 1706
dfea7acc
DS
1707 file.InsertLine(sTmp, nIndex);
1708 nIndex++;
b13d92d1 1709 }
dfea7acc
DS
1710
1711 bTemp = file.Write();
1712 file.Close();
b13d92d1 1713 }
dfea7acc 1714
2b813b73 1715 return bTemp;
b13d92d1
VZ
1716}
1717
dfea7acc 1718wxFileType * wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
b9517a0a 1719{
2b813b73 1720 InitIfNeeded();
b9517a0a 1721
dfea7acc
DS
1722 wxString strType = ftInfo.GetMimeType();
1723 wxString strDesc = ftInfo.GetDescription();
1724 wxString strIcon = ftInfo.GetIconFile();
2b813b73 1725
dfea7acc 1726 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
2b813b73 1727
678ebfcd 1728 if ( ! ftInfo.GetOpenCommand().empty())
dfea7acc
DS
1729 entry->Add(wxT("open=") + ftInfo.GetOpenCommand() + wxT(" %s "));
1730 if ( ! ftInfo.GetPrintCommand().empty())
1731 entry->Add(wxT("print=") + ftInfo.GetPrintCommand() + wxT(" %s "));
2b813b73
VZ
1732
1733 // now find where these extensions are in the data store and remove them
dfea7acc 1734 wxArrayString sA_Exts = ftInfo.GetExtensions();
2b813b73
VZ
1735 wxString sExt, sExtStore;
1736 size_t i, nIndex;
1737 for (i=0; i < sA_Exts.GetCount(); i++)
dfea7acc 1738 {
2b813b73 1739 sExt = sA_Exts.Item(i);
dfea7acc
DS
1740
1741 // clean up to just a space before and after
d0ee33f5 1742 sExt.Trim().Trim(false);
2b813b73 1743 sExt = wxT(' ') + sExt + wxT(' ');
7c2e5dec 1744 for (nIndex = 0; nIndex < m_aExtensions.GetCount(); nIndex++)
dfea7acc 1745 {
2b813b73 1746 sExtStore = m_aExtensions.Item(nIndex);
dfea7acc
DS
1747 if (sExtStore.Replace(sExt, wxT(" ") ) > 0)
1748 m_aExtensions.Item(nIndex) = sExtStore;
1749 }
2b813b73 1750 }
b9517a0a 1751
dfea7acc 1752 if ( !DoAssociation(strType, strIcon, entry, sA_Exts, strDesc) )
2b813b73 1753 return NULL;
4d2976ad 1754
2b813b73 1755 return GetFileTypeFromMimeType(strType);
4d2976ad
VS
1756}
1757
2b813b73
VZ
1758bool wxMimeTypesManagerImpl::DoAssociation(const wxString& strType,
1759 const wxString& strIcon,
678ebfcd 1760 wxMimeTypeCommands *entry,
2b813b73
VZ
1761 const wxArrayString& strExtensions,
1762 const wxString& strDesc)
b13d92d1 1763{
d0ee33f5 1764 int nIndex = AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
7520f3da 1765
2b813b73 1766 if ( nIndex == wxNOT_FOUND )
d0ee33f5 1767 return false;
b13d92d1 1768
dfea7acc 1769 return WriteMimeInfo(nIndex, false);
b13d92d1
VZ
1770}
1771
2b813b73 1772bool wxMimeTypesManagerImpl::WriteMimeInfo(int nIndex, bool delete_mime )
b13d92d1 1773{
d0ee33f5 1774 bool ok = true;
b13d92d1 1775
a06c1b9b 1776 if ( m_mailcapStylesInited & wxMAILCAP_STANDARD )
2b813b73
VZ
1777 {
1778 // write in metamail format;
dfea7acc
DS
1779 if (WriteToMimeTypes(nIndex, delete_mime) )
1780 if ( WriteToMailCap(nIndex, delete_mime) )
d0ee33f5 1781 ok = false;
b13d92d1 1782 }
dfea7acc 1783
2b813b73 1784 if ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE )
b9517a0a 1785 {
2b813b73 1786 // write in netsacpe format;
dfea7acc
DS
1787 if (WriteToNSMimeTypes(nIndex, delete_mime) )
1788 if ( WriteToMailCap(nIndex, delete_mime) )
d0ee33f5 1789 ok = false;
2b813b73 1790 }
dfea7acc 1791
2b850ae1
RR
1792 // Don't write GNOME files here as this is not
1793 // allowed and simply doesn't work
dfea7acc 1794
2b813b73
VZ
1795 if (m_mailcapStylesInited & wxMAILCAP_KDE)
1796 {
1797 // write in KDE format;
dfea7acc 1798 if (WriteKDEMimeFile(nIndex, delete_mime) )
d0ee33f5 1799 ok = false;
b9517a0a
VZ
1800 }
1801
2b813b73 1802 return ok;
a6c65e88
VZ
1803}
1804
2b813b73
VZ
1805int wxMimeTypesManagerImpl::AddToMimeData(const wxString& strType,
1806 const wxString& strIcon,
678ebfcd 1807 wxMimeTypeCommands *entry,
2b813b73
VZ
1808 const wxArrayString& strExtensions,
1809 const wxString& strDesc,
678ebfcd 1810 bool replaceExisting)
b13d92d1 1811{
2b813b73 1812 InitIfNeeded();
b13d92d1 1813
2b813b73 1814 // ensure mimetype is always lower case
678ebfcd
VZ
1815 wxString mimeType = strType.Lower();
1816
1817 // is this a known MIME type?
2b813b73
VZ
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);
678ebfcd 1824 m_aEntries.Add(entry ? entry : new wxMimeTypeCommands);
b13d92d1 1825
678ebfcd 1826 // change nIndex so we can use it below to add the extensions
df5168c4 1827 m_aExtensions.Add(wxEmptyString);
b8d61021 1828 nIndex = m_aExtensions.size() - 1;
678ebfcd
VZ
1829
1830 m_aDescriptions.Add(strDesc);
b13d92d1 1831 }
678ebfcd 1832 else // yes, we already have it
2b813b73 1833 {
678ebfcd 1834 if ( replaceExisting )
2b813b73
VZ
1835 {
1836 // if new description change it
678ebfcd 1837 if ( !strDesc.empty())
2b813b73
VZ
1838 m_aDescriptions[nIndex] = strDesc;
1839
1840 // if new icon change it
678ebfcd 1841 if ( !strIcon.empty())
2b813b73
VZ
1842 m_aIcons[nIndex] = strIcon;
1843
678ebfcd
VZ
1844 if ( entry )
1845 {
1846 delete m_aEntries[nIndex];
1847 m_aEntries[nIndex] = entry;
1848 }
2b813b73 1849 }
678ebfcd 1850 else // add data we don't already have ...
2b813b73 1851 {
2b813b73 1852 // if new description add only if none
678ebfcd 1853 if ( m_aDescriptions[nIndex].empty() )
2b813b73
VZ
1854 m_aDescriptions[nIndex] = strDesc;
1855
1856 // if new icon and no existing icon
dfea7acc 1857 if ( m_aIcons[nIndex].empty() )
2b813b73
VZ
1858 m_aIcons[nIndex] = strIcon;
1859
2b813b73 1860 // add any new entries...
678ebfcd 1861 if ( entry )
2b813b73 1862 {
7c2e5dec 1863 wxMimeTypeCommands *entryOld = m_aEntries[nIndex];
678ebfcd
VZ
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 }
2b293fc7
VZ
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;
2b813b73
VZ
1878 }
1879 }
b13d92d1 1880 }
4d2976ad 1881
678ebfcd
VZ
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 {
dfea7acc 1889 wxString ext = strExtensions[i] + wxT(' ');
678ebfcd
VZ
1890
1891 if ( exts.Find(ext) == wxNOT_FOUND )
1892 {
1893 exts += ext;
1894 }
1895 }
1896
2b813b73
VZ
1897 // check data integrity
1898 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
678ebfcd
VZ
1899 m_aTypes.Count() == m_aExtensions.Count() &&
1900 m_aTypes.Count() == m_aIcons.Count() &&
1901 m_aTypes.Count() == m_aDescriptions.Count() );
cf471cab 1902
2b813b73 1903 return nIndex;
cf471cab
VS
1904}
1905
dfea7acc 1906wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
b13d92d1 1907{
678ebfcd 1908 if (ext.empty() )
2b813b73
VZ
1909 return NULL;
1910
a6c65e88
VZ
1911 InitIfNeeded();
1912
1ee17e1c 1913 size_t count = m_aExtensions.GetCount();
2b813b73 1914 for ( size_t n = 0; n < count; n++ )
678ebfcd 1915 {
dfea7acc 1916 wxStringTokenizer tk(m_aExtensions[n], wxT(' '));
1ee17e1c 1917
678ebfcd
VZ
1918 while ( tk.HasMoreTokens() )
1919 {
1ee17e1c 1920 // consider extensions as not being case-sensitive
d0ee33f5 1921 if ( tk.GetNextToken().IsSameAs(ext, false /* no case */) )
678ebfcd 1922 {
1ee17e1c 1923 // found
678ebfcd 1924 wxFileType *fileType = new wxFileType;
1ee17e1c 1925 fileType->m_impl->Init(this, n);
678ebfcd
VZ
1926
1927 return fileType;
1ee17e1c
VZ
1928 }
1929 }
1930 }
b13d92d1 1931
678ebfcd 1932 return NULL;
b13d92d1
VZ
1933}
1934
7c2e5dec 1935wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
b13d92d1 1936{
a6c65e88
VZ
1937 InitIfNeeded();
1938
2b813b73 1939 wxFileType * fileType = NULL;
b13d92d1
VZ
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);
2b813b73
VZ
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();
dfea7acc
DS
1960 for ( size_t n = 0; n < nCount; n++ )
1961 {
2b813b73 1962 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
dfea7acc
DS
1963 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") )
1964 {
2b813b73
VZ
1965 index = n;
1966 break;
b13d92d1
VZ
1967 }
1968 }
1969
2b813b73 1970 if ( index != wxNOT_FOUND )
dfea7acc
DS
1971 {
1972 // don't throw away fileType that was already found
1973 if (!fileType)
7bc5ddc6 1974 fileType = new wxFileType;
b13d92d1 1975 fileType->m_impl->Init(this, index);
b13d92d1 1976 }
dfea7acc 1977
2b813b73
VZ
1978 return fileType;
1979}
1980
2b813b73
VZ
1981wxString wxMimeTypesManagerImpl::GetCommand(const wxString & verb, size_t nIndex) const
1982{
1983 wxString command, testcmd, sV, sTmp;
1984 sV = verb + wxT("=");
dfea7acc 1985
2b813b73 1986 // list of verb = command pairs for this mimetype
678ebfcd 1987 wxMimeTypeCommands * sPairs = m_aEntries [nIndex];
2b813b73
VZ
1988
1989 size_t i;
678ebfcd 1990 for ( i = 0; i < sPairs->GetCount (); i++ )
2b813b73 1991 {
678ebfcd
VZ
1992 sTmp = sPairs->GetVerbCmd (i);
1993 if ( sTmp.Contains(sV) )
1994 command = sTmp.AfterFirst(wxT('='));
b13d92d1 1995 }
dfea7acc 1996
2b813b73 1997 return command;
b13d92d1
VZ
1998}
1999
8e124873
VZ
2000void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
2001{
a6c65e88
VZ
2002 InitIfNeeded();
2003
3f1aaa16 2004 wxString extensions;
8e124873
VZ
2005 const wxArrayString& exts = filetype.GetExtensions();
2006 size_t nExts = exts.GetCount();
dfea7acc
DS
2007 for ( size_t nExt = 0; nExt < nExts; nExt++ )
2008 {
2009 if ( nExt > 0 )
223d09f6 2010 extensions += wxT(' ');
dfea7acc 2011
8e124873
VZ
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(),
223d09f6 2022 wxT(""),
8e124873
VZ
2023 filetype.GetDescription());
2024}
2025
2026void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
2027 const wxString& strExtensions,
2028 const wxString& strDesc)
2029{
2b813b73
VZ
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;
a6c65e88 2035
2b813b73 2036 wxArrayString sExts;
d0ee33f5 2037 sTmp.Trim().Trim(false);
2b813b73 2038
678ebfcd 2039 while (!sTmp.empty())
2b813b73 2040 {
dfea7acc 2041 sExts.Add(sTmp.AfterLast(wxT(' ')));
2b813b73 2042 sTmp = sTmp.BeforeLast(wxT(' '));
8e124873 2043 }
2b813b73 2044
dfea7acc 2045 AddToMimeData(strMimeType, strIcon, NULL, sExts, strDesc, true);
8e124873
VZ
2046}
2047
2048void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
2049 const wxString& strOpenCmd,
2050 const wxString& strPrintCmd,
2051 const wxString& strTest,
2052 const wxString& strDesc)
2053{
a6c65e88
VZ
2054 InitIfNeeded();
2055
678ebfcd 2056 wxMimeTypeCommands *entry = new wxMimeTypeCommands;
2b813b73
VZ
2057 entry->Add(wxT("open=") + strOpenCmd);
2058 entry->Add(wxT("print=") + strPrintCmd);
2059 entry->Add(wxT("test=") + strTest);
8e124873 2060
2b813b73
VZ
2061 wxString strIcon;
2062 wxArrayString strExtensions;
2063
dfea7acc 2064 AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
8e124873
VZ
2065}
2066
cc385968 2067bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
b13d92d1 2068{
f6bcfd97
BP
2069 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
2070 strFileName.c_str());
b13d92d1
VZ
2071
2072 wxTextFile file(strFileName);
2b5f62a0 2073#if defined(__WXGTK20__) && wxUSE_UNICODE
7c2e5dec 2074 if ( !file.Open(wxConvUTF8) )
2b5f62a0 2075#else
b13d92d1 2076 if ( !file.Open() )
2b5f62a0 2077#endif
d0ee33f5 2078 return false;
b13d92d1
VZ
2079
2080 // the information we extract
2081 wxString strMimeType, strDesc, strExtensions;
2082
2083 size_t nLineCount = file.GetLineCount();
50920146 2084 const wxChar *pc = NULL;
2b5f62a0
VZ
2085 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2086 {
dfea7acc
DS
2087 if ( pc == NULL )
2088 {
22b4634c
VZ
2089 // now we're at the start of the line
2090 pc = file[nLine].c_str();
2091 }
dfea7acc
DS
2092 else
2093 {
22b4634c
VZ
2094 // we didn't finish with the previous line yet
2095 nLine--;
2096 }
b13d92d1
VZ
2097
2098 // skip whitespace
50920146 2099 while ( wxIsspace(*pc) )
b13d92d1
VZ
2100 pc++;
2101
54acce90 2102 // comment or blank line?
dfea7acc
DS
2103 if ( *pc == wxT('#') || !*pc )
2104 {
22b4634c
VZ
2105 // skip the whole line
2106 pc = NULL;
b13d92d1 2107 continue;
22b4634c 2108 }
b13d92d1
VZ
2109
2110 // detect file format
223d09f6 2111 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
dfea7acc
DS
2112 if ( pEqualSign == NULL )
2113 {
b13d92d1
VZ
2114 // brief format
2115 // ------------
2116
2117 // first field is mime type
dfea7acc
DS
2118 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ )
2119 {
b13d92d1
VZ
2120 strMimeType += *pc;
2121 }
2122
2123 // skip whitespace
50920146 2124 while ( wxIsspace(*pc) )
b13d92d1
VZ
2125 pc++;
2126
2127 // take all the rest of the string
2128 strExtensions = pc;
2129
2130 // no description...
2131 strDesc.Empty();
2132 }
dfea7acc
DS
2133 else
2134 {
b13d92d1
VZ
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
50920146 2142 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
678ebfcd 2143 ;
b13d92d1 2144
50920146 2145 const wxChar *pEnd;
dfea7acc
DS
2146 if ( *pc == wxT('"') )
2147 {
b13d92d1 2148 // the string is quoted and ends at the matching quote
223d09f6 2149 pEnd = wxStrchr(++pc, wxT('"'));
dfea7acc
DS
2150 if ( pEnd == NULL )
2151 {
5d0d3e6d 2152 wxLogWarning(wxT("Mime.types file %s, line %lu: unterminated quoted string."),
985f824c 2153 strFileName.c_str(), nLine + 1L);
b13d92d1
VZ
2154 }
2155 }
dfea7acc
DS
2156 else
2157 {
8862e11b
VZ
2158 // unquoted string ends at the first space or at the end of
2159 // line
2160 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
678ebfcd 2161 ;
b13d92d1
VZ
2162 }
2163
2164 // now we have the RHS (field value)
2165 wxString strRHS(pc, pEnd - pc);
2166
22b4634c 2167 // check what follows this entry
dfea7acc
DS
2168 if ( *pEnd == wxT('"') )
2169 {
b13d92d1
VZ
2170 // skip this quote
2171 pEnd++;
2172 }
2173
50920146 2174 for ( pc = pEnd; wxIsspace(*pc); pc++ )
678ebfcd 2175 ;
b13d92d1 2176
22b4634c
VZ
2177 // if there is something left, it may be either a '\\' to continue
2178 // the line or the next field of the same entry
dfea7acc
DS
2179 bool entryEnded = *pc == wxT('\0');
2180 bool nextFieldOnSameLine = false;
2181 if ( !entryEnded )
2182 {
223d09f6 2183 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
b13d92d1 2184 }
b13d92d1
VZ
2185
2186 // now see what we got
dfea7acc
DS
2187 if ( strLHS == wxT("type") )
2188 {
b13d92d1
VZ
2189 strMimeType = strRHS;
2190 }
dfea7acc
DS
2191 else if ( strLHS.StartsWith(wxT("desc")) )
2192 {
b13d92d1
VZ
2193 strDesc = strRHS;
2194 }
dfea7acc
DS
2195 else if ( strLHS == wxT("exts") )
2196 {
b13d92d1
VZ
2197 strExtensions = strRHS;
2198 }
dfea7acc 2199 else if ( strLHS == wxT("icon") )
70687b63 2200 {
a2717c3d
VZ
2201 // this one is simply ignored: it usually refers to Netscape
2202 // built in icons which are useless for us anyhow
70687b63 2203 }
dfea7acc 2204 else if ( !strLHS.StartsWith(wxT("x-")) )
70687b63
VZ
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
5d0d3e6d 2210 wxLogWarning(wxT("Unknown field in file %s, line %lu: '%s'."),
985f824c 2211 strFileName.c_str(), nLine + 1L, strLHS.c_str());
b13d92d1
VZ
2212 }
2213
dfea7acc
DS
2214 if ( !entryEnded )
2215 {
22b4634c
VZ
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
b13d92d1 2221 // will be interpreted correctly.
22b4634c 2222
b13d92d1
VZ
2223 continue;
2224 }
2225 }
2226
a6c65e88
VZ
2227 // depending on the format (Mosaic or Netscape) either space or comma
2228 // is used to separate the extensions
223d09f6 2229 strExtensions.Replace(wxT(","), wxT(" "));
a1d8eaf7
VZ
2230
2231 // also deal with the leading dot
678ebfcd 2232 if ( !strExtensions.empty() && strExtensions[0u] == wxT('.') )
1b986aef 2233 {
a1d8eaf7
VZ
2234 strExtensions.erase(0, 1);
2235 }
2236
678ebfcd
VZ
2237 wxLogTrace(TRACE_MIME, wxT("mime.types: '%s' => '%s' (%s)"),
2238 strExtensions.c_str(),
2239 strMimeType.c_str(),
2240 strDesc.c_str());
2b813b73 2241
8e124873 2242 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
22b4634c
VZ
2243
2244 // finished with this line
2245 pc = NULL;
b13d92d1
VZ
2246 }
2247
d0ee33f5 2248 return true;
b13d92d1
VZ
2249}
2250
678ebfcd
VZ
2251// ----------------------------------------------------------------------------
2252// UNIX mailcap files parsing
2253// ----------------------------------------------------------------------------
2254
2255// the data for a single MIME type
2256struct 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
d0ee33f5 2273 MailcapLineData() { testfailed = needsterminal = copiousoutput = false; }
678ebfcd
VZ
2274};
2275
2276// process a non-standard (i.e. not the first or second one) mailcap field
2277bool
2278wxMimeTypesManagerImpl::ProcessOtherMailcapField(MailcapLineData& data,
2279 const wxString& curField)
2280{
2281 if ( curField.empty() )
2282 {
2283 // we don't care
d0ee33f5 2284 return true;
678ebfcd
VZ
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
d0ee33f5
WS
2295 lhs.Trim(true); // from right
2296 rhs.Trim(false); // from left
678ebfcd
VZ
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());
678ebfcd
VZ
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
d0ee33f5 2320 data.testfailed = true;
678ebfcd
VZ
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 {
d0ee33f5 2346 data.needsterminal = true;
678ebfcd
VZ
2347 }
2348 else if ( curField == wxT("copiousoutput"))
2349 {
2350 // copiousoutput impies that the viewer is a console program
2351 data.needsterminal =
d0ee33f5 2352 data.copiousoutput = true;
678ebfcd
VZ
2353 }
2354 else if ( !IsKnownUnimportantField(curField) )
2355 {
d0ee33f5 2356 return false;
678ebfcd
VZ
2357 }
2358 }
2359
d0ee33f5 2360 return true;
678ebfcd
VZ
2361}
2362
cc385968
VZ
2363bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
2364 bool fallback)
b13d92d1 2365{
f6bcfd97
BP
2366 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
2367 strFileName.c_str());
b13d92d1
VZ
2368
2369 wxTextFile file(strFileName);
2b5f62a0 2370#if defined(__WXGTK20__) && wxUSE_UNICODE
7c2e5dec 2371 if ( !file.Open(wxConvUTF8) )
2b5f62a0 2372#else
b13d92d1 2373 if ( !file.Open() )
2b5f62a0 2374#endif
d0ee33f5 2375 return false;
b13d92d1 2376
678ebfcd
VZ
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);
b13d92d1
VZ
2385
2386 size_t nLineCount = file.GetLineCount();
678ebfcd
VZ
2387 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2388 {
b13d92d1 2389 // now we're at the start of the line
50920146 2390 const wxChar *pc = file[nLine].c_str();
b13d92d1
VZ
2391
2392 // skip whitespace
50920146 2393 while ( wxIsspace(*pc) )
b13d92d1
VZ
2394 pc++;
2395
2396 // comment or empty string?
223d09f6 2397 if ( *pc == wxT('#') || *pc == wxT('\0') )
b13d92d1
VZ
2398 continue;
2399
2400 // no, do parse
678ebfcd 2401 // ------------
b13d92d1
VZ
2402
2403 // what field are we currently in? The first 2 are fixed and there may
678ebfcd
VZ
2404 // be an arbitrary number of other fields parsed by
2405 // ProcessOtherMailcapField()
2406 //
2407 // the first field is the MIME type
b13d92d1
VZ
2408 enum
2409 {
2410 Field_Type,
2411 Field_OpenCmd,
2412 Field_Other
dfea7acc
DS
2413 }
2414 currentToken = Field_Type;
b13d92d1
VZ
2415
2416 // the flags and field values on the current line
678ebfcd
VZ
2417 MailcapLineData data;
2418
d0ee33f5 2419 bool cont = true;
678ebfcd
VZ
2420 while ( cont )
2421 {
2422 switch ( *pc )
2423 {
223d09f6 2424 case wxT('\\'):
b13d92d1
VZ
2425 // interpret the next character literally (notice that
2426 // backslash can be used for line continuation)
678ebfcd
VZ
2427 if ( *++pc == wxT('\0') )
2428 {
6e358ae7 2429 // fetch the next line if there is one
678ebfcd
VZ
2430 if ( nLine == nLineCount - 1 )
2431 {
6e358ae7 2432 // something is wrong, bail out
d0ee33f5 2433 cont = false;
6e358ae7 2434
76a6e803 2435 wxLogDebug(wxT("Mailcap file %s, line %lu: '\\' on the end of the last line ignored."),
6e358ae7 2436 strFileName.c_str(),
985f824c 2437 nLine + 1L);
6e358ae7 2438 }
678ebfcd
VZ
2439 else
2440 {
6e358ae7
VZ
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 }
b13d92d1 2447 }
678ebfcd
VZ
2448 else
2449 {
b13d92d1
VZ
2450 // just a normal character
2451 curField += *pc;
2452 }
2453 break;
2454
223d09f6 2455 case wxT('\0'):
d0ee33f5 2456 cont = false; // end of line reached, exit the loop
b13d92d1 2457
678ebfcd 2458 // fall through to still process this field
b13d92d1 2459
223d09f6 2460 case wxT(';'):
b13d92d1 2461 // trim whitespaces from both sides
d0ee33f5 2462 curField.Trim(true).Trim(false);
b13d92d1 2463
678ebfcd
VZ
2464 switch ( currentToken )
2465 {
b13d92d1 2466 case Field_Type:
678ebfcd
VZ
2467 data.type = curField.Lower();
2468 if ( data.type.empty() )
2469 {
6e358ae7
VZ
2470 // I don't think that this is a valid mailcap
2471 // entry, but try to interpret it somehow
dfea7acc 2472 data.type = wxT('*');
6e358ae7
VZ
2473 }
2474
678ebfcd
VZ
2475 if ( data.type.Find(wxT('/')) == wxNOT_FOUND )
2476 {
b13d92d1 2477 // we interpret "type" as "type/*"
678ebfcd 2478 data.type += wxT("/*");
b13d92d1
VZ
2479 }
2480
2481 currentToken = Field_OpenCmd;
2482 break;
2483
2484 case Field_OpenCmd:
678ebfcd 2485 data.cmdOpen = curField;
b13d92d1
VZ
2486
2487 currentToken = Field_Other;
2488 break;
2489
2490 case Field_Other:
678ebfcd
VZ
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 (
76a6e803 2499 wxT("Mailcap file %s, line %lu: unknown field '%s' for the MIME type '%s' ignored."),
678ebfcd 2500 strFileName.c_str(),
985f824c 2501 nLine + 1L,
678ebfcd
VZ
2502 curField.c_str(),
2503 data.type.c_str()
2504 );
2505 }
2506 else if ( data.testfailed )
2507 {
2508 // skip this entry entirely
d0ee33f5 2509 cont = false;
b13d92d1
VZ
2510 }
2511
2512 // it already has this value
2513 //currentToken = Field_Other;
2514 break;
2515
2516 default:
223d09f6 2517 wxFAIL_MSG(wxT("unknown field type in mailcap"));
b13d92d1
VZ
2518 }
2519
2520 // next token starts immediately after ';'
2521 curField.Empty();
2522 break;
2523
2524 default:
2525 curField += *pc;
2526 }
6e358ae7
VZ
2527
2528 // continue in the same line
2529 pc++;
b13d92d1
VZ
2530 }
2531
678ebfcd
VZ
2532 // we read the entire entry, check what have we got
2533 // ------------------------------------------------
2534
b13d92d1 2535 // check that we really read something reasonable
678ebfcd
VZ
2536 if ( currentToken < Field_Other )
2537 {
5d0d3e6d 2538 wxLogWarning(wxT("Mailcap file %s, line %lu: incomplete entry ignored."),
985f824c 2539 strFileName.c_str(), nLine + 1L);
678ebfcd
VZ
2540
2541 continue;
b13d92d1 2542 }
e1e9ea40 2543
7c2e5dec 2544 // if the test command failed, it's as if the entry were not there at all
678ebfcd
VZ
2545 if ( data.testfailed )
2546 {
2547 continue;
2548 }
2b813b73 2549
678ebfcd
VZ
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 {
dfea7acc
DS
2560 const wxChar *p = wxGetenv(wxT("PAGER"));
2561 data.cmdOpen << wxT(" | ") << (p ? p : wxT("more"));
678ebfcd 2562 }
b13d92d1 2563
678ebfcd
VZ
2564 if ( data.needsterminal )
2565 {
dfea7acc 2566 data.cmdOpen = wxString::Format(wxT("xterm -e sh -c '%s'"),
2b5f62a0 2567 data.cmdOpen.c_str());
678ebfcd 2568 }
b13d92d1 2569
678ebfcd
VZ
2570 if ( !data.cmdOpen.empty() )
2571 {
dfea7acc 2572 data.verbs.Insert(wxT("open"), 0);
678ebfcd
VZ
2573 data.commands.Insert(data.cmdOpen, 0);
2574 }
2b813b73 2575
678ebfcd
VZ
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 {
d0ee33f5 2583 overwrite = false;
678ebfcd
VZ
2584 }
2585 else
2586 {
2587 // have we seen this one before?
2588 int nIndex = m_aTypes.Index(data.type);
2589
a9a76b2f
VZ
2590 // and if we have, was it in this file? if not, we should
2591 // overwrite the previously seen one
678ebfcd 2592 overwrite = nIndex == wxNOT_FOUND ||
a9a76b2f 2593 aIndicesSeenHere.Index(nIndex) == wxNOT_FOUND;
b13d92d1
VZ
2594 }
2595
dfea7acc 2596 wxLogTrace(TRACE_MIME, wxT("mailcap %s: %s [%s]"),
678ebfcd 2597 data.type.c_str(), data.cmdOpen.c_str(),
dfea7acc 2598 overwrite ? wxT("replace") : wxT("add"));
678ebfcd
VZ
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 }
b13d92d1 2614 }
cc385968 2615
d0ee33f5 2616 return true;
b13d92d1
VZ
2617}
2618
696e1ea0 2619size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1b986aef 2620{
a6c65e88
VZ
2621 InitIfNeeded();
2622
54acce90
VZ
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];
dfea7acc 2631 if ( type.Find(wxT('*')) == wxNOT_FOUND )
54acce90
VZ
2632 {
2633 mimetypes.Add(type);
2634 }
2635 }
1b986aef 2636
54acce90 2637 return mimetypes.GetCount();
1b986aef
VZ
2638}
2639
a6c65e88
VZ
2640// ----------------------------------------------------------------------------
2641// writing to MIME type files
2642// ----------------------------------------------------------------------------
2643
2b813b73 2644bool wxMimeTypesManagerImpl::Unassociate(wxFileType *ft)
a6c65e88 2645{
9413e1e3
VZ
2646 InitIfNeeded();
2647
2b813b73 2648 wxArrayString sMimeTypes;
dfea7acc 2649 ft->GetMimeTypes(sMimeTypes);
a6c65e88 2650
2b813b73
VZ
2651 wxString sMime;
2652 size_t i;
2653 for (i = 0; i < sMimeTypes.GetCount(); i ++)
2654 {
2655 sMime = sMimeTypes.Item(i);
dfea7acc 2656 int nIndex = m_aTypes.Index(sMime);
2b813b73
VZ
2657 if ( nIndex == wxNOT_FOUND)
2658 {
2659 // error if we get here ??
d0ee33f5 2660 return false;
2b813b73
VZ
2661 }
2662 else
2663 {
dfea7acc 2664 WriteMimeInfo(nIndex, true);
e9d9f136
VZ
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);
2b813b73
VZ
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
d0ee33f5 2678 return true;
a6c65e88
VZ
2679}
2680
f6bcfd97
BP
2681// ----------------------------------------------------------------------------
2682// private functions
2683// ----------------------------------------------------------------------------
2684
2685static bool IsKnownUnimportantField(const wxString& fieldAll)
2686{
2687 static const wxChar *knownFields[] =
2688 {
dfea7acc
DS
2689 wxT("x-mozilla-flags"),
2690 wxT("nametemplate"),
2691 wxT("textualnewlines"),
f6bcfd97
BP
2692 };
2693
dfea7acc 2694 wxString field = fieldAll.BeforeFirst(wxT('='));
f6bcfd97
BP
2695 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
2696 {
2697 if ( field.CmpNoCase(knownFields[n]) == 0 )
d0ee33f5 2698 return true;
f6bcfd97
BP
2699 }
2700
d0ee33f5 2701 return false;
f6bcfd97
BP
2702}
2703
8e124873 2704#endif
b79e32cc 2705 // wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE