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