]> git.saurik.com Git - wxWidgets.git/blame - src/unix/mimetype.cpp
readded back SetFullName
[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>
9// Licence: wxWindows license (part of wxExtra library)
10/////////////////////////////////////////////////////////////////////////////
11
f6bcfd97
BP
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20#ifdef __GNUG__
21 #pragma implementation "mimetype.h"
b13d92d1
VZ
22#endif
23
b13d92d1
VZ
24// for compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
ce4169a4 28 #pragma hdrstop
b13d92d1
VZ
29#endif
30
b13d92d1 31#ifndef WX_PRECOMP
ce4169a4
RR
32 #include "wx/defs.h"
33#endif
34
f6bcfd97 35#if wxUSE_FILE && wxUSE_TEXTFILE
ce4169a4
RR
36
37#ifndef WX_PRECOMP
38 #include "wx/string.h"
b12915c1
VZ
39 #if wxUSE_GUI
40 #include "wx/icon.h"
41 #endif
b13d92d1
VZ
42#endif //WX_PRECOMP
43
3d05544e 44
b13d92d1 45#include "wx/log.h"
ce4169a4 46#include "wx/file.h"
b13d92d1
VZ
47#include "wx/intl.h"
48#include "wx/dynarray.h"
2432b92d 49#include "wx/confbase.h"
b13d92d1 50
7dc3cc31
VS
51#include "wx/ffile.h"
52#include "wx/textfile.h"
53#include "wx/dir.h"
54#include "wx/utils.h"
55#include "wx/tokenzr.h"
b13d92d1 56
7dc3cc31 57#include "wx/unix/mimetype.h"
b13d92d1
VZ
58
59// other standard headers
60#include <ctype.h>
61
b12915c1
VZ
62// in case we're compiling in non-GUI mode
63class WXDLLEXPORT wxIcon;
64
f6bcfd97
BP
65// ----------------------------------------------------------------------------
66// constants
67// ----------------------------------------------------------------------------
68
69// MIME code tracing mask
70#define TRACE_MIME _T("mime")
71
72// ----------------------------------------------------------------------------
73// private functions
74// ----------------------------------------------------------------------------
75
76// there are some fields which we don't understand but for which we don't give
77// warnings as we know that they're not important - this function is used to
78// test for them
79static bool IsKnownUnimportantField(const wxString& field);
80
b13d92d1
VZ
81// ----------------------------------------------------------------------------
82// private classes
83// ----------------------------------------------------------------------------
84
b13d92d1
VZ
85
86// this class uses both mailcap and mime.types to gather information about file
87// types.
88//
89// The information about mailcap file was extracted from metamail(1) sources and
90// documentation.
91//
92// Format of mailcap file: spaces are ignored, each line is either a comment
93// (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
94// A backslash can be used to quote semicolons and newlines (and, in fact,
95// anything else including itself).
96//
97// The first field is always the MIME type in the form of type/subtype (see RFC
98// 822) where subtype may be '*' meaning "any". Following metamail, we accept
99// "type" which means the same as "type/*", although I'm not sure whether this
100// is standard.
101//
102// The second field is always the command to run. It is subject to
103// parameter/filename expansion described below.
104//
105// All the following fields are optional and may not be present at all. If
106// they're present they may appear in any order, although each of them should
107// appear only once. The optional fields are the following:
108// * notes=xxx is an uninterpreted string which is silently ignored
109// * test=xxx is the command to be used to determine whether this mailcap line
110// applies to our data or not. The RHS of this field goes through the
111// parameter/filename expansion (as the 2nd field) and the resulting string
112// is executed. The line applies only if the command succeeds, i.e. returns 0
113// exit code.
114// * print=xxx is the command to be used to print (and not view) the data of
115// this type (parameter/filename expansion is done here too)
116// * edit=xxx is the command to open/edit the data of this type
117// * needsterminal means that a new console must be created for the viewer
118// * copiousoutput means that the viewer doesn't interact with the user but
119// produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
120// good example), thus it might be a good idea to use some kind of paging
121// mechanism.
122// * textualnewlines means not to perform CR/LF translation (not honored)
123// * compose and composetyped fields are used to determine the program to be
124// called to create a new message pert in the specified format (unused).
125//
ec4768ef 126// Parameter/filename xpansion:
b13d92d1
VZ
127// * %s is replaced with the (full) file name
128// * %t is replaced with MIME type/subtype of the entry
129// * for multipart type only %n is replaced with the nnumber of parts and %F is
130// replaced by an array of (content-type, temporary file name) pairs for all
131// message parts (TODO)
132// * %{parameter} is replaced with the value of parameter taken from
133// Content-type header line of the message.
134//
135// FIXME any docs with real descriptions of these files??
136//
137// There are 2 possible formats for mime.types file, one entry per line (used
138// for global mime.types) and "expanded" format where an entry takes multiple
139// lines (used for users mime.types).
140//
141// For both formats spaces are ignored and lines starting with a '#' are
142// comments. Each record has one of two following forms:
143// a) for "brief" format:
144// <mime type> <space separated list of extensions>
ec4768ef 145// b) for "expanded" format:
b13d92d1
VZ
146// type=<mime type> \ desc="<description>" \ exts="ext"
147//
148// We try to autodetect the format of mime.types: if a non-comment line starts
149// with "type=" we assume the second format, otherwise the first one.
150
151// there may be more than one entry for one and the same mime type, to
152// choose the right one we have to run the command specified in the test
153// field on our data.
154class MailCapEntry
155{
156public:
157 // ctor
158 MailCapEntry(const wxString& openCmd,
159 const wxString& printCmd,
160 const wxString& testCmd)
161 : m_openCmd(openCmd), m_printCmd(printCmd), m_testCmd(testCmd)
162 {
163 m_next = NULL;
164 }
cf471cab
VS
165
166 ~MailCapEntry()
167 {
168 if (m_next) delete m_next;
169 }
b13d92d1
VZ
170
171 // accessors
172 const wxString& GetOpenCmd() const { return m_openCmd; }
173 const wxString& GetPrintCmd() const { return m_printCmd; }
174 const wxString& GetTestCmd() const { return m_testCmd; }
175
176 MailCapEntry *GetNext() const { return m_next; }
177
178 // operations
179 // prepend this element to the list
180 void Prepend(MailCapEntry *next) { m_next = next; }
cc385968
VZ
181 // insert into the list at given position
182 void Insert(MailCapEntry *next, size_t pos)
183 {
184 // FIXME slooow...
185 MailCapEntry *cur;
186 size_t n = 0;
187 for ( cur = next; cur != NULL; cur = cur->m_next, n++ ) {
188 if ( n == pos )
189 break;
190 }
191
223d09f6 192 wxASSERT_MSG( n == pos, wxT("invalid position in MailCapEntry::Insert") );
cc385968
VZ
193
194 m_next = cur->m_next;
195 cur->m_next = this;
196 }
197 // append this element to the list
b13d92d1
VZ
198 void Append(MailCapEntry *next)
199 {
223d09f6 200 wxCHECK_RET( next != NULL, wxT("Append()ing to what?") );
cc385968 201
b13d92d1
VZ
202 // FIXME slooow...
203 MailCapEntry *cur;
204 for ( cur = next; cur->m_next != NULL; cur = cur->m_next )
205 ;
206
207 cur->m_next = this;
208
223d09f6 209 wxASSERT_MSG( !m_next, wxT("Append()ing element already in the list?") );
b13d92d1
VZ
210 }
211
212private:
213 wxString m_openCmd, // command to use to open/view the file
214 m_printCmd, // print
215 m_testCmd; // only apply this entry if test yields
216 // true (i.e. the command returns 0)
217
218 MailCapEntry *m_next; // in the linked list
219};
220
b13d92d1 221
b9517a0a
VZ
222// the base class which may be used to find an icon for the MIME type
223class wxMimeTypeIconHandler
224{
225public:
226 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon) = 0;
5bd3a2da
VZ
227
228 // this function fills manager with MIME types information gathered
cdf339c9
VS
229 // (as side effect) when searching for icons. This may be particularly
230 // useful if mime.types is incomplete (e.g. RedHat distributions).
231 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager) = 0;
b9517a0a
VZ
232};
233
b9517a0a
VZ
234
235// the icon handler which uses GNOME MIME database
236class wxGNOMEIconHandler : public wxMimeTypeIconHandler
237{
238public:
239 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
4d2976ad 240 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
5bd3a2da 241
b9517a0a
VZ
242private:
243 void Init();
244 void LoadIconsFromKeyFile(const wxString& filename);
245 void LoadKeyFilesFromDir(const wxString& dirbase);
246
4d2976ad
VS
247 void LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager);
248 void LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager);
249
b9517a0a
VZ
250 static bool m_inited;
251
252 static wxSortedArrayString ms_mimetypes;
253 static wxArrayString ms_icons;
254};
255
256// the icon handler which uses KDE MIME database
257class wxKDEIconHandler : public wxMimeTypeIconHandler
258{
259public:
260 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
cdf339c9 261 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
b9517a0a
VZ
262
263private:
264 void LoadLinksForMimeSubtype(const wxString& dirbase,
265 const wxString& subdir,
cdf339c9
VS
266 const wxString& filename,
267 const wxArrayString& icondirs);
b9517a0a 268 void LoadLinksForMimeType(const wxString& dirbase,
cdf339c9
VS
269 const wxString& subdir,
270 const wxArrayString& icondirs);
5bd3a2da 271 void LoadLinkFilesFromDir(const wxString& dirbase,
cdf339c9 272 const wxArrayString& icondirs);
b9517a0a
VZ
273 void Init();
274
275 static bool m_inited;
276
277 static wxSortedArrayString ms_mimetypes;
278 static wxArrayString ms_icons;
cdf339c9
VS
279
280 static wxArrayString ms_infoTypes;
281 static wxArrayString ms_infoDescriptions;
282 static wxArrayString ms_infoExtensions;
b9517a0a
VZ
283};
284
b13d92d1 285
b9517a0a
VZ
286
287// ----------------------------------------------------------------------------
288// various statics
289// ----------------------------------------------------------------------------
290
291static wxGNOMEIconHandler gs_iconHandlerGNOME;
292static wxKDEIconHandler gs_iconHandlerKDE;
293
294bool wxGNOMEIconHandler::m_inited = FALSE;
295wxSortedArrayString wxGNOMEIconHandler::ms_mimetypes;
296wxArrayString wxGNOMEIconHandler::ms_icons;
297
298bool wxKDEIconHandler::m_inited = FALSE;
299wxSortedArrayString wxKDEIconHandler::ms_mimetypes;
300wxArrayString wxKDEIconHandler::ms_icons;
301
cdf339c9
VS
302wxArrayString wxKDEIconHandler::ms_infoTypes;
303wxArrayString wxKDEIconHandler::ms_infoDescriptions;
304wxArrayString wxKDEIconHandler::ms_infoExtensions;
305
306
b9517a0a
VZ
307ArrayIconHandlers wxMimeTypesManagerImpl::ms_iconHandlers;
308
309// ----------------------------------------------------------------------------
310// wxGNOMEIconHandler
311// ----------------------------------------------------------------------------
312
313// GNOME stores the info we're interested in in several locations:
314// 1. xxx.keys files under /usr/share/mime-info
315// 2. xxx.keys files under ~/.gnome/mime-info
316//
317// The format of xxx.keys file is the following:
318//
319// mimetype/subtype:
320// field=value
321//
322// with blank lines separating the entries and indented lines starting with
323// TABs. We're interested in the field icon-filename whose value is the path
324// containing the icon.
afaee315
VZ
325//
326// Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
327// just before the field name.
b9517a0a
VZ
328
329void wxGNOMEIconHandler::LoadIconsFromKeyFile(const wxString& filename)
330{
331 wxTextFile textfile(filename);
332 if ( !textfile.Open() )
333 return;
334
335 // values for the entry being parsed
336 wxString curMimeType, curIconFile;
337
338 const wxChar *pc;
339 size_t nLineCount = textfile.GetLineCount();
340 for ( size_t nLine = 0; ; nLine++ )
341 {
342 if ( nLine < nLineCount )
343 {
344 pc = textfile[nLine].c_str();
345 if ( *pc == _T('#') )
346 {
347 // skip comments
348 continue;
349 }
350 }
351 else
352 {
353 // so that we will fall into the "if" below
354 pc = NULL;
355 }
356
357 if ( !pc || !*pc )
358 {
359 // end of the entry
360 if ( !!curMimeType && !!curIconFile )
361 {
362 // do we already know this mimetype?
363 int i = ms_mimetypes.Index(curMimeType);
364 if ( i == wxNOT_FOUND )
365 {
366 // add a new entry
367 size_t n = ms_mimetypes.Add(curMimeType);
368 ms_icons.Insert(curIconFile, n);
369 }
370 else
371 {
372 // replace the existing one (this means that the directories
373 // should be searched in order of increased priority!)
374 ms_icons[(size_t)i] = curIconFile;
375 }
376 }
377
378 if ( !pc )
379 {
380 // the end - this can only happen if nLine == nLineCount
381 break;
382 }
383
384 curIconFile.Empty();
385
386 continue;
387 }
388
389 // what do we have here?
390 if ( *pc == _T('\t') )
391 {
392 // this is a field=value ling
393 pc++; // skip leading TAB
394
afaee315
VZ
395 // skip optional "[lang]"
396 if ( *pc == _T('[') )
397 {
398 while ( *pc )
399 {
400 if ( *pc++ == _T(']') )
401 break;
402 }
403 }
404
b9517a0a
VZ
405 static const int lenField = 13; // strlen("icon-filename")
406 if ( wxStrncmp(pc, _T("icon-filename"), lenField) == 0 )
407 {
408 // skip '=' which follows and take everything left until the end
409 // of line
410 curIconFile = pc + lenField + 1;
411 }
412 //else: some other field, we don't care
413 }
414 else
415 {
416 // this is the start of the new section
417 curMimeType.Empty();
418
419 while ( *pc != _T(':') && *pc != _T('\0') )
420 {
421 curMimeType += *pc++;
422 }
4d2976ad
VS
423 }
424 }
425}
426
427void wxGNOMEIconHandler::LoadKeyFilesFromDir(const wxString& dirbase)
428{
429 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
430 _T("base directory shouldn't end with a slash") );
431
432 wxString dirname = dirbase;
433 dirname << _T("/mime-info");
434
435 if ( !wxDir::Exists(dirname) )
436 return;
437
438 wxDir dir(dirname);
439 if ( !dir.IsOpened() )
440 return;
441
442 // we will concatenate it with filename to get the full path below
443 dirname += _T('/');
444
445 wxString filename;
446 bool cont = dir.GetFirst(&filename, _T("*.keys"), wxDIR_FILES);
447 while ( cont )
448 {
449 LoadIconsFromKeyFile(dirname + filename);
450
451 cont = dir.GetNext(&filename);
452 }
453}
454
455
456void wxGNOMEIconHandler::LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager)
457{
458 wxTextFile textfile(filename);
459 if ( !textfile.Open() )
460 return;
461
462 // values for the entry being parsed
463 wxString curMimeType, curExtList;
464
465 const wxChar *pc;
466 size_t nLineCount = textfile.GetLineCount();
467 for ( size_t nLine = 0; ; nLine++ )
468 {
469 if ( nLine < nLineCount )
470 {
471 pc = textfile[nLine].c_str();
472 if ( *pc == _T('#') )
473 {
474 // skip comments
475 continue;
476 }
477 }
478 else
479 {
480 // so that we will fall into the "if" below
481 pc = NULL;
482 }
b9517a0a 483
4d2976ad
VS
484 if ( !pc || !*pc )
485 {
486 // end of the entry
487 if ( !!curMimeType && !!curExtList )
b9517a0a 488 {
4d2976ad
VS
489 manager -> AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
490 }
b9517a0a 491
4d2976ad
VS
492 if ( !pc )
493 {
494 // the end - this can only happen if nLine == nLineCount
b9517a0a
VZ
495 break;
496 }
4d2976ad
VS
497
498 curExtList.Empty();
499
500 continue;
501 }
502
503 // what do we have here?
504 if ( *pc == _T('\t') )
505 {
506 // this is a field=value ling
507 pc++; // skip leading TAB
508
509 static const int lenField = 4; // strlen("ext:")
510 if ( wxStrncmp(pc, _T("ext:"), lenField) == 0 )
511 {
512 // skip ' ' which follows and take everything left until the end
513 // of line
514 curExtList = pc + lenField + 1;
515 }
516 //else: some other field, we don't care
517 }
518 else
519 {
520 // this is the start of the new section
521 curMimeType.Empty();
522
523 while ( *pc != _T(':') && *pc != _T('\0') )
524 {
525 curMimeType += *pc++;
526 }
b9517a0a
VZ
527 }
528 }
529}
530
4d2976ad
VS
531
532void wxGNOMEIconHandler::LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager)
b9517a0a
VZ
533{
534 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
535 _T("base directory shouldn't end with a slash") );
536
537 wxString dirname = dirbase;
538 dirname << _T("/mime-info");
539
540 if ( !wxDir::Exists(dirname) )
541 return;
542
543 wxDir dir(dirname);
544 if ( !dir.IsOpened() )
545 return;
546
547 // we will concatenate it with filename to get the full path below
548 dirname += _T('/');
549
550 wxString filename;
4d2976ad 551 bool cont = dir.GetFirst(&filename, _T("*.mime"), wxDIR_FILES);
b9517a0a
VZ
552 while ( cont )
553 {
4d2976ad 554 LoadMimeTypesFromMimeFile(dirname + filename, manager);
b9517a0a
VZ
555
556 cont = dir.GetNext(&filename);
557 }
558}
559
4d2976ad 560
b9517a0a
VZ
561void wxGNOMEIconHandler::Init()
562{
563 wxArrayString dirs;
564 dirs.Add(_T("/usr/share"));
be0a33fb 565 dirs.Add(_T("/usr/local/share"));
fb5ddb4c 566
af159c44
RR
567 wxString gnomedir;
568 wxGetHomeDir( &gnomedir );
569 gnomedir += _T("/.gnome");
570 dirs.Add( gnomedir );
b9517a0a
VZ
571
572 size_t nDirs = dirs.GetCount();
573 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
574 {
575 LoadKeyFilesFromDir(dirs[nDir]);
576 }
577
578 m_inited = TRUE;
579}
580
4d2976ad
VS
581
582void wxGNOMEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
583{
584 if ( !m_inited )
585 {
586 Init();
587 }
588
589 wxArrayString dirs;
590 dirs.Add(_T("/usr/share"));
591 dirs.Add(_T("/usr/local/share"));
592
593 wxString gnomedir;
594 wxGetHomeDir( &gnomedir );
595 gnomedir += _T("/.gnome");
596 dirs.Add( gnomedir );
597
598 size_t nDirs = dirs.GetCount();
599 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
600 {
601 LoadMimeFilesFromDir(dirs[nDir], manager);
602 }
603}
604
6dc6fda6
VZ
605#if wxUSE_GUI
606 #define WXUNUSED_UNLESS_GUI(p) p
607#else
608 #define WXUNUSED_UNLESS_GUI(p)
609#endif
4d2976ad 610
6dc6fda6
VZ
611bool wxGNOMEIconHandler::GetIcon(const wxString& mimetype,
612 wxIcon * WXUNUSED_UNLESS_GUI(icon))
b9517a0a
VZ
613{
614 if ( !m_inited )
615 {
616 Init();
617 }
618
619 int index = ms_mimetypes.Index(mimetype);
620 if ( index == wxNOT_FOUND )
621 return FALSE;
622
623 wxString iconname = ms_icons[(size_t)index];
624
625#if wxUSE_GUI
be0a33fb
VS
626 wxLogNull nolog;
627 wxIcon icn;
628 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
629 icn = wxIcon(iconname);
630 else
631 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
6dc6fda6
VZ
632 if ( !icn.Ok() )
633 return FALSE;
634
635 if ( icon )
636 *icon = icn;
b9517a0a
VZ
637#else
638 // helpful for testing in console mode
639 wxLogDebug(_T("Found GNOME icon for '%s': '%s'\n"),
640 mimetype.c_str(), iconname.c_str());
641#endif
642
643 return TRUE;
644}
645
646// ----------------------------------------------------------------------------
647// wxKDEIconHandler
648// ----------------------------------------------------------------------------
649
650// KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
651// may be found in either of the following locations
652//
509a6196 653// 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
b9517a0a
VZ
654// 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
655//
656// The format of a .kdelnk file is almost the same as the one used by
657// wxFileConfig, i.e. there are groups, comments and entries. The icon is the
658// value for the entry "Type"
659
660void wxKDEIconHandler::LoadLinksForMimeSubtype(const wxString& dirbase,
661 const wxString& subdir,
cdf339c9
VS
662 const wxString& filename,
663 const wxArrayString& icondirs)
b9517a0a
VZ
664{
665 wxFFile file(dirbase + filename);
666 if ( !file.IsOpened() )
667 return;
668
cdf339c9
VS
669 // construct mimetype from the directory name and the basename of the
670 // file (it always has .kdelnk extension)
671 wxString mimetype;
672 mimetype << subdir << _T('/') << filename.BeforeLast(_T('.'));
673
b9517a0a
VZ
674 // these files are small, slurp the entire file at once
675 wxString text;
676 if ( !file.ReadAll(&text) )
677 return;
678
cdf339c9
VS
679 int pos;
680 const wxChar *pc;
681
682 // before trying to find an icon, grab mimetype information
683 // (because BFU's machine would hardly have well-edited mime.types but (s)he might
684 // have edited it in control panel...)
685
686 wxString mime_extension, mime_desc;
687
5bd3a2da 688 pos = wxNOT_FOUND;
cdf339c9
VS
689 if (wxGetLocale() != NULL)
690 mime_desc = _T("Comment[") + wxGetLocale()->GetName() + _T("]=");
691 if (pos == wxNOT_FOUND) mime_desc = _T("Comment=");
692 pos = text.Find(mime_desc);
693 if (pos == wxNOT_FOUND) mime_desc = wxEmptyString;
694 else
695 {
696 pc = text.c_str() + pos + mime_desc.Length();
697 mime_desc = wxEmptyString;
698 while ( *pc && *pc != _T('\n') ) mime_desc += *pc++;
699 }
700
701 pos = text.Find(_T("Patterns="));
702 if (pos != wxNOT_FOUND)
703 {
704 wxString exts;
705 pc = text.c_str() + pos + 9;
706 while ( *pc && *pc != _T('\n') ) exts += *pc++;
707 wxStringTokenizer tokenizer(exts, _T(";"));
708 wxString e;
5bd3a2da 709
cdf339c9
VS
710 while (tokenizer.HasMoreTokens())
711 {
712 e = tokenizer.GetNextToken();
713 if (e.Left(2) != _T("*.")) continue; // don't support too difficult patterns
714 mime_extension << e.Mid(2);
715 mime_extension << _T(' ');
716 }
717 mime_extension.RemoveLast();
718 }
5bd3a2da 719
cdf339c9
VS
720 ms_infoTypes.Add(mimetype);
721 ms_infoDescriptions.Add(mime_desc);
722 ms_infoExtensions.Add(mime_extension);
723
724 // ok, now we can take care of icon:
725
726 pos = text.Find(_T("Icon="));
b9517a0a
VZ
727 if ( pos == wxNOT_FOUND )
728 {
729 // no icon info
730 return;
731 }
732
733 wxString icon;
734
cdf339c9 735 pc = text.c_str() + pos + 5; // 5 == strlen("Icon=")
b9517a0a
VZ
736 while ( *pc && *pc != _T('\n') )
737 {
738 icon += *pc++;
739 }
740
741 if ( !!icon )
742 {
cdf339c9
VS
743 // we must check if the file exists because it may be stored
744 // in many locations, at least ~/.kde and $KDEDIR
745 size_t nDir, nDirs = icondirs.GetCount();
746 for ( nDir = 0; nDir < nDirs; nDir++ )
747 if (wxFileExists(icondirs[nDir] + icon))
748 {
749 icon.Prepend(icondirs[nDir]);
750 break;
751 }
752 if (nDir == nDirs) return; //does not exist
b9517a0a
VZ
753
754 // do we already have this MIME type?
755 int i = ms_mimetypes.Index(mimetype);
756 if ( i == wxNOT_FOUND )
757 {
758 // add it
759 size_t n = ms_mimetypes.Add(mimetype);
760 ms_icons.Insert(icon, n);
761 }
762 else
763 {
764 // replace the old value
765 ms_icons[(size_t)i] = icon;
766 }
767 }
768}
769
770void wxKDEIconHandler::LoadLinksForMimeType(const wxString& dirbase,
cdf339c9
VS
771 const wxString& subdir,
772 const wxArrayString& icondirs)
b9517a0a
VZ
773{
774 wxString dirname = dirbase;
775 dirname += subdir;
776 wxDir dir(dirname);
777 if ( !dir.IsOpened() )
778 return;
779
780 dirname += _T('/');
781
782 wxString filename;
783 bool cont = dir.GetFirst(&filename, _T("*.kdelnk"), wxDIR_FILES);
784 while ( cont )
785 {
cdf339c9 786 LoadLinksForMimeSubtype(dirname, subdir, filename, icondirs);
b9517a0a
VZ
787
788 cont = dir.GetNext(&filename);
789 }
790}
791
cdf339c9
VS
792void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString& dirbase,
793 const wxArrayString& icondirs)
b9517a0a
VZ
794{
795 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
796 _T("base directory shouldn't end with a slash") );
797
798 wxString dirname = dirbase;
799 dirname << _T("/mimelnk");
800
801 if ( !wxDir::Exists(dirname) )
802 return;
803
804 wxDir dir(dirname);
805 if ( !dir.IsOpened() )
806 return;
807
808 // we will concatenate it with dir name to get the full path below
809 dirname += _T('/');
810
811 wxString subdir;
812 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
813 while ( cont )
814 {
cdf339c9 815 LoadLinksForMimeType(dirname, subdir, icondirs);
b9517a0a
VZ
816
817 cont = dir.GetNext(&subdir);
818 }
819}
820
821void wxKDEIconHandler::Init()
822{
823 wxArrayString dirs;
cdf339c9
VS
824 wxArrayString icondirs;
825
826 // settings in ~/.kde have maximal priority
827 dirs.Add(wxGetHomeDir() + _T("/.kde/share"));
828 icondirs.Add(wxGetHomeDir() + _T("/.kde/share/icons/"));
509a6196
VZ
829
830 // the variable KDEDIR is set when KDE is running
831 const char *kdedir = getenv("KDEDIR");
832 if ( kdedir )
833 {
834 dirs.Add(wxString(kdedir) + _T("/share"));
cdf339c9 835 icondirs.Add(wxString(kdedir) + _T("/share/icons/"));
509a6196
VZ
836 }
837 else
838 {
839 // try to guess KDEDIR
840 dirs.Add(_T("/usr/share"));
841 dirs.Add(_T("/opt/kde/share"));
cdf339c9 842 icondirs.Add(_T("/usr/share/icons/"));
13e3b45a 843 icondirs.Add(_T("/usr/X11R6/share/icons/")); // Debian/Corel linux
cdf339c9 844 icondirs.Add(_T("/opt/kde/share/icons/"));
509a6196
VZ
845 }
846
b9517a0a
VZ
847 size_t nDirs = dirs.GetCount();
848 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
849 {
cdf339c9 850 LoadLinkFilesFromDir(dirs[nDir], icondirs);
b9517a0a
VZ
851 }
852
853 m_inited = TRUE;
854}
855
6dc6fda6
VZ
856bool wxKDEIconHandler::GetIcon(const wxString& mimetype,
857 wxIcon * WXUNUSED_UNLESS_GUI(icon))
b9517a0a
VZ
858{
859 if ( !m_inited )
860 {
861 Init();
862 }
863
864 int index = ms_mimetypes.Index(mimetype);
865 if ( index == wxNOT_FOUND )
866 return FALSE;
867
868 wxString iconname = ms_icons[(size_t)index];
869
870#if wxUSE_GUI
be0a33fb
VS
871 wxLogNull nolog;
872 wxIcon icn;
873 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
874 icn = wxIcon(iconname);
875 else
876 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
6dc6fda6
VZ
877
878 if ( !icn.Ok() )
879 return FALSE;
880
881 if ( icon )
882 *icon = icn;
b9517a0a
VZ
883#else
884 // helpful for testing in console mode
885 wxLogDebug(_T("Found KDE icon for '%s': '%s'\n"),
886 mimetype.c_str(), iconname.c_str());
887#endif
888
889 return TRUE;
890}
891
cdf339c9
VS
892
893void wxKDEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
894{
895 if ( !m_inited ) Init();
5bd3a2da 896
cdf339c9
VS
897 size_t cnt = ms_infoTypes.GetCount();
898 for (unsigned i = 0; i < cnt; i++)
899 manager -> AddMimeTypeInfo(ms_infoTypes[i], ms_infoExtensions[i], ms_infoDescriptions[i]);
900}
901
902
b9517a0a
VZ
903// ----------------------------------------------------------------------------
904// wxFileTypeImpl (Unix)
905// ----------------------------------------------------------------------------
906
b13d92d1
VZ
907MailCapEntry *
908wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const
909{
910 wxString command;
4d2976ad 911 MailCapEntry *entry = m_manager->m_aEntries[m_index[0]];
b13d92d1 912 while ( entry != NULL ) {
cc385968 913 // notice that an empty command would always succeed (it's ok)
b13d92d1
VZ
914 command = wxFileType::ExpandCommand(entry->GetTestCmd(), params);
915
f6bcfd97
BP
916 // suppress the command output
917 if ( !command.IsEmpty() )
918 {
919 if ( wxSystem(command) == 0 ) {
920 // ok, passed
921 wxLogTrace(TRACE_MIME,
922 wxT("Test '%s' for mime type '%s' succeeded."),
923 command.c_str(), params.GetMimeType().c_str());
924 break;
925 }
926 else {
927 wxLogTrace(TRACE_MIME,
928 wxT("Test '%s' for mime type '%s' failed."),
929 command.c_str(), params.GetMimeType().c_str());
930 }
b13d92d1
VZ
931 }
932
933 entry = entry->GetNext();
934 }
935
936 return entry;
937}
938
b9517a0a
VZ
939bool wxFileTypeImpl::GetIcon(wxIcon *icon) const
940{
4d2976ad
VS
941 wxArrayString mimetypes;
942 GetMimeTypes(mimetypes);
b9517a0a
VZ
943
944 ArrayIconHandlers& handlers = m_manager->GetIconHandlers();
945 size_t count = handlers.GetCount();
4d2976ad 946 size_t counttypes = mimetypes.GetCount();
b9517a0a
VZ
947 for ( size_t n = 0; n < count; n++ )
948 {
4d2976ad
VS
949 for ( size_t n2 = 0; n2 < counttypes; n2++ )
950 {
951 if ( handlers[n]->GetIcon(mimetypes[n2], icon) )
952 return TRUE;
953 }
b9517a0a
VZ
954 }
955
956 return FALSE;
957}
958
4d2976ad
VS
959
960bool
961wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
962{
963 mimeTypes.Clear();
964 for (size_t i = 0; i < m_index.GetCount(); i++)
965 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
966 return TRUE;
967}
968
969
b13d92d1
VZ
970bool
971wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd,
972 const wxFileType::MessageParameters& params,
973 bool open) const
974{
975 MailCapEntry *entry = GetEntry(params);
976 if ( entry == NULL ) {
977 // all tests failed...
978 return FALSE;
979 }
980
981 wxString cmd = open ? entry->GetOpenCmd() : entry->GetPrintCmd();
982 if ( cmd.IsEmpty() ) {
983 // may happen, especially for "print"
984 return FALSE;
985 }
986
987 *expandedCmd = wxFileType::ExpandCommand(cmd, params);
988 return TRUE;
989}
990
991bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
992{
4d2976ad 993 wxString strExtensions = m_manager->GetExtension(m_index[0]);
b13d92d1
VZ
994 extensions.Empty();
995
996 // one extension in the space or comma delimitid list
997 wxString strExt;
50920146 998 for ( const wxChar *p = strExtensions; ; p++ ) {
223d09f6 999 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') ) {
b13d92d1
VZ
1000 if ( !strExt.IsEmpty() ) {
1001 extensions.Add(strExt);
1002 strExt.Empty();
1003 }
1004 //else: repeated spaces (shouldn't happen, but it's not that
1005 // important if it does happen)
1006
223d09f6 1007 if ( *p == wxT('\0') )
b13d92d1
VZ
1008 break;
1009 }
223d09f6 1010 else if ( *p == wxT('.') ) {
b13d92d1
VZ
1011 // remove the dot from extension (but only if it's the first char)
1012 if ( !strExt.IsEmpty() ) {
223d09f6 1013 strExt += wxT('.');
b13d92d1
VZ
1014 }
1015 //else: no, don't append it
1016 }
1017 else {
1018 strExt += *p;
1019 }
1020 }
1021
1022 return TRUE;
1023}
1024
b9517a0a
VZ
1025// ----------------------------------------------------------------------------
1026// wxMimeTypesManagerImpl (Unix)
1027// ----------------------------------------------------------------------------
1028
1029/* static */
1030ArrayIconHandlers& wxMimeTypesManagerImpl::GetIconHandlers()
1031{
1032 if ( ms_iconHandlers.GetCount() == 0 )
1033 {
be0a33fb 1034 ms_iconHandlers.Add(&gs_iconHandlerGNOME);
4d2976ad 1035 ms_iconHandlers.Add(&gs_iconHandlerKDE);
b9517a0a
VZ
1036 }
1037
1038 return ms_iconHandlers;
1039}
1040
b13d92d1
VZ
1041// read system and user mailcaps (TODO implement mime.types support)
1042wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1043{
1044 // directories where we look for mailcap and mime.types by default
1045 // (taken from metamail(1) sources)
50920146 1046 static const wxChar *aStandardLocations[] =
b13d92d1 1047 {
223d09f6
KB
1048 wxT("/etc"),
1049 wxT("/usr/etc"),
1050 wxT("/usr/local/etc"),
1051 wxT("/etc/mail"),
1052 wxT("/usr/public/lib")
b13d92d1
VZ
1053 };
1054
1055 // first read the system wide file(s)
5bd3a2da
VZ
1056 size_t n;
1057 for ( n = 0; n < WXSIZEOF(aStandardLocations); n++ ) {
b13d92d1
VZ
1058 wxString dir = aStandardLocations[n];
1059
223d09f6 1060 wxString file = dir + wxT("/mailcap");
b13d92d1
VZ
1061 if ( wxFile::Exists(file) ) {
1062 ReadMailcap(file);
1063 }
1064
223d09f6 1065 file = dir + wxT("/mime.types");
b13d92d1
VZ
1066 if ( wxFile::Exists(file) ) {
1067 ReadMimeTypes(file);
1068 }
1069 }
1070
223d09f6 1071 wxString strHome = wxGetenv(wxT("HOME"));
b13d92d1
VZ
1072
1073 // and now the users mailcap
223d09f6 1074 wxString strUserMailcap = strHome + wxT("/.mailcap");
b13d92d1
VZ
1075 if ( wxFile::Exists(strUserMailcap) ) {
1076 ReadMailcap(strUserMailcap);
1077 }
1078
1079 // read the users mime.types
223d09f6 1080 wxString strUserMimeTypes = strHome + wxT("/.mime.types");
b13d92d1
VZ
1081 if ( wxFile::Exists(strUserMimeTypes) ) {
1082 ReadMimeTypes(strUserMimeTypes);
1083 }
4d2976ad
VS
1084
1085 // read KDE/GNOME tables
1086 ArrayIconHandlers& handlers = GetIconHandlers();
1087 size_t count = handlers.GetCount();
1088 for ( size_t hn = 0; hn < count; hn++ )
1089 handlers[hn]->GetMimeInfoRecords(this);
b13d92d1
VZ
1090}
1091
cf471cab
VS
1092
1093wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1094{
1095 size_t cnt = m_aEntries.GetCount();
1096 for (size_t i = 0; i < cnt; i++) delete m_aEntries[i];
1097}
1098
1099
b13d92d1
VZ
1100wxFileType *
1101wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1102{
4d2976ad 1103 wxFileType *fileType = NULL;
1ee17e1c
VZ
1104 size_t count = m_aExtensions.GetCount();
1105 for ( size_t n = 0; n < count; n++ ) {
1106 wxString extensions = m_aExtensions[n];
1107 while ( !extensions.IsEmpty() ) {
223d09f6
KB
1108 wxString field = extensions.BeforeFirst(wxT(' '));
1109 extensions = extensions.AfterFirst(wxT(' '));
1ee17e1c
VZ
1110
1111 // consider extensions as not being case-sensitive
ec4768ef 1112 if ( field.IsSameAs(ext, FALSE /* no case */) ) {
1ee17e1c 1113 // found
4d2976ad 1114 if (fileType == NULL) fileType = new wxFileType;
1ee17e1c 1115 fileType->m_impl->Init(this, n);
4d2976ad 1116 // adds this mime type to _list_ of mime types with this extension
1ee17e1c
VZ
1117 }
1118 }
1119 }
b13d92d1 1120
4d2976ad 1121 return fileType;
b13d92d1
VZ
1122}
1123
1124wxFileType *
1125wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1126{
1127 // mime types are not case-sensitive
1128 wxString mimetype(mimeType);
1129 mimetype.MakeLower();
1130
1131 // first look for an exact match
1132 int index = m_aTypes.Index(mimetype);
3c67202d 1133 if ( index == wxNOT_FOUND ) {
b13d92d1 1134 // then try to find "text/*" as match for "text/plain" (for example)
3c67202d
VZ
1135 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1136 // the whole string - ok.
223d09f6 1137 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
b13d92d1
VZ
1138
1139 size_t nCount = m_aTypes.Count();
1140 for ( size_t n = 0; n < nCount; n++ ) {
223d09f6
KB
1141 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1142 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") ) {
b13d92d1
VZ
1143 index = n;
1144 break;
1145 }
1146 }
1147 }
1148
3c67202d 1149 if ( index != wxNOT_FOUND ) {
b13d92d1
VZ
1150 wxFileType *fileType = new wxFileType;
1151 fileType->m_impl->Init(this, index);
1152
1153 return fileType;
1154 }
1155 else {
1156 // not found...
1157 return NULL;
1158 }
1159}
1160
8e124873
VZ
1161void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1162{
3f1aaa16 1163 wxString extensions;
8e124873
VZ
1164 const wxArrayString& exts = filetype.GetExtensions();
1165 size_t nExts = exts.GetCount();
1166 for ( size_t nExt = 0; nExt < nExts; nExt++ ) {
1167 if ( nExt > 0 ) {
223d09f6 1168 extensions += wxT(' ');
8e124873
VZ
1169 }
1170 extensions += exts[nExt];
1171 }
1172
1173 AddMimeTypeInfo(filetype.GetMimeType(),
1174 extensions,
1175 filetype.GetDescription());
1176
1177 AddMailcapInfo(filetype.GetMimeType(),
1178 filetype.GetOpenCommand(),
1179 filetype.GetPrintCommand(),
223d09f6 1180 wxT(""),
8e124873
VZ
1181 filetype.GetDescription());
1182}
1183
1184void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
1185 const wxString& strExtensions,
1186 const wxString& strDesc)
1187{
1188 int index = m_aTypes.Index(strMimeType);
1189 if ( index == wxNOT_FOUND ) {
1190 // add a new entry
1191 m_aTypes.Add(strMimeType);
1192 m_aEntries.Add(NULL);
1193 m_aExtensions.Add(strExtensions);
1194 m_aDescriptions.Add(strDesc);
1195 }
1196 else {
1197 // modify an existing one
1198 if ( !strDesc.IsEmpty() ) {
1199 m_aDescriptions[index] = strDesc; // replace old value
1200 }
c15d098c 1201 m_aExtensions[index] += ' ' + strExtensions;
8e124873
VZ
1202 }
1203}
1204
1205void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
1206 const wxString& strOpenCmd,
1207 const wxString& strPrintCmd,
1208 const wxString& strTest,
1209 const wxString& strDesc)
1210{
1211 MailCapEntry *entry = new MailCapEntry(strOpenCmd, strPrintCmd, strTest);
1212
1213 int nIndex = m_aTypes.Index(strType);
1214 if ( nIndex == wxNOT_FOUND ) {
1215 // new file type
1216 m_aTypes.Add(strType);
1217
1218 m_aEntries.Add(entry);
223d09f6 1219 m_aExtensions.Add(wxT(""));
8e124873
VZ
1220 m_aDescriptions.Add(strDesc);
1221 }
1222 else {
1223 // always append the entry in the tail of the list - info added with
1224 // this function can only come from AddFallbacks()
1225 MailCapEntry *entryOld = m_aEntries[nIndex];
1226 if ( entryOld )
1227 entry->Append(entryOld);
1228 else
1229 m_aEntries[nIndex] = entry;
1230 }
1231}
1232
cc385968 1233bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
b13d92d1 1234{
f6bcfd97
BP
1235 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
1236 strFileName.c_str());
b13d92d1
VZ
1237
1238 wxTextFile file(strFileName);
1239 if ( !file.Open() )
cc385968 1240 return FALSE;
b13d92d1
VZ
1241
1242 // the information we extract
1243 wxString strMimeType, strDesc, strExtensions;
1244
1245 size_t nLineCount = file.GetLineCount();
50920146 1246 const wxChar *pc = NULL;
b13d92d1 1247 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
22b4634c
VZ
1248 if ( pc == NULL ) {
1249 // now we're at the start of the line
1250 pc = file[nLine].c_str();
1251 }
1252 else {
1253 // we didn't finish with the previous line yet
1254 nLine--;
1255 }
b13d92d1
VZ
1256
1257 // skip whitespace
50920146 1258 while ( wxIsspace(*pc) )
b13d92d1
VZ
1259 pc++;
1260
54acce90
VZ
1261 // comment or blank line?
1262 if ( *pc == wxT('#') || !*pc ) {
22b4634c
VZ
1263 // skip the whole line
1264 pc = NULL;
b13d92d1 1265 continue;
22b4634c 1266 }
b13d92d1
VZ
1267
1268 // detect file format
223d09f6 1269 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
b13d92d1
VZ
1270 if ( pEqualSign == NULL ) {
1271 // brief format
1272 // ------------
1273
1274 // first field is mime type
223d09f6 1275 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ ) {
b13d92d1
VZ
1276 strMimeType += *pc;
1277 }
1278
1279 // skip whitespace
50920146 1280 while ( wxIsspace(*pc) )
b13d92d1
VZ
1281 pc++;
1282
1283 // take all the rest of the string
1284 strExtensions = pc;
1285
1286 // no description...
1287 strDesc.Empty();
1288 }
1289 else {
1290 // expanded format
1291 // ---------------
1292
1293 // the string on the left of '=' is the field name
1294 wxString strLHS(pc, pEqualSign - pc);
1295
1296 // eat whitespace
50920146 1297 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
b13d92d1
VZ
1298 ;
1299
50920146 1300 const wxChar *pEnd;
223d09f6 1301 if ( *pc == wxT('"') ) {
b13d92d1 1302 // the string is quoted and ends at the matching quote
223d09f6 1303 pEnd = wxStrchr(++pc, wxT('"'));
b13d92d1
VZ
1304 if ( pEnd == NULL ) {
1305 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1306 "quoted string."),
1307 strFileName.c_str(), nLine + 1);
1308 }
1309 }
1310 else {
8862e11b
VZ
1311 // unquoted string ends at the first space or at the end of
1312 // line
1313 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
b13d92d1
VZ
1314 ;
1315 }
1316
1317 // now we have the RHS (field value)
1318 wxString strRHS(pc, pEnd - pc);
1319
22b4634c 1320 // check what follows this entry
223d09f6 1321 if ( *pEnd == wxT('"') ) {
b13d92d1
VZ
1322 // skip this quote
1323 pEnd++;
1324 }
1325
50920146 1326 for ( pc = pEnd; wxIsspace(*pc); pc++ )
b13d92d1
VZ
1327 ;
1328
22b4634c
VZ
1329 // if there is something left, it may be either a '\\' to continue
1330 // the line or the next field of the same entry
223d09f6 1331 bool entryEnded = *pc == wxT('\0'),
22b4634c
VZ
1332 nextFieldOnSameLine = FALSE;
1333 if ( !entryEnded ) {
223d09f6 1334 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
b13d92d1 1335 }
b13d92d1
VZ
1336
1337 // now see what we got
223d09f6 1338 if ( strLHS == wxT("type") ) {
b13d92d1
VZ
1339 strMimeType = strRHS;
1340 }
223d09f6 1341 else if ( strLHS == wxT("desc") ) {
b13d92d1
VZ
1342 strDesc = strRHS;
1343 }
223d09f6 1344 else if ( strLHS == wxT("exts") ) {
b13d92d1
VZ
1345 strExtensions = strRHS;
1346 }
1347 else {
1348 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1349 strFileName.c_str(), nLine + 1, strLHS.c_str());
1350 }
1351
1352 if ( !entryEnded ) {
22b4634c
VZ
1353 if ( !nextFieldOnSameLine )
1354 pc = NULL;
1355 //else: don't reset it
1356
1357 // as we don't reset strMimeType, the next field in this entry
b13d92d1 1358 // will be interpreted correctly.
22b4634c 1359
b13d92d1
VZ
1360 continue;
1361 }
1362 }
1363
a1d8eaf7
VZ
1364 // although it doesn't seem to be covered by RFCs, some programs
1365 // (notably Netscape) create their entries with several comma
1366 // separated extensions (RFC mention the spaces only)
223d09f6 1367 strExtensions.Replace(wxT(","), wxT(" "));
a1d8eaf7
VZ
1368
1369 // also deal with the leading dot
1b986aef
VZ
1370 if ( !strExtensions.IsEmpty() && strExtensions[0u] == wxT('.') )
1371 {
a1d8eaf7
VZ
1372 strExtensions.erase(0, 1);
1373 }
1374
8e124873 1375 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
22b4634c
VZ
1376
1377 // finished with this line
1378 pc = NULL;
b13d92d1
VZ
1379 }
1380
1381 // check our data integriry
1382 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1383 m_aTypes.Count() == m_aExtensions.Count() &&
1384 m_aTypes.Count() == m_aDescriptions.Count() );
cc385968
VZ
1385
1386 return TRUE;
b13d92d1
VZ
1387}
1388
cc385968
VZ
1389bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
1390 bool fallback)
b13d92d1 1391{
f6bcfd97
BP
1392 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
1393 strFileName.c_str());
b13d92d1
VZ
1394
1395 wxTextFile file(strFileName);
1396 if ( !file.Open() )
cc385968 1397 return FALSE;
b13d92d1 1398
cc385968
VZ
1399 // see the comments near the end of function for the reason we need these
1400 // variables (search for the next occurence of them)
1401 // indices of MIME types (in m_aTypes) we already found in this file
b13d92d1 1402 wxArrayInt aEntryIndices;
cc385968
VZ
1403 // aLastIndices[n] is the index of last element in
1404 // m_aEntries[aEntryIndices[n]] from this file
1405 wxArrayInt aLastIndices;
b13d92d1
VZ
1406
1407 size_t nLineCount = file.GetLineCount();
1408 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1409 // now we're at the start of the line
50920146 1410 const wxChar *pc = file[nLine].c_str();
b13d92d1
VZ
1411
1412 // skip whitespace
50920146 1413 while ( wxIsspace(*pc) )
b13d92d1
VZ
1414 pc++;
1415
1416 // comment or empty string?
223d09f6 1417 if ( *pc == wxT('#') || *pc == wxT('\0') )
b13d92d1
VZ
1418 continue;
1419
1420 // no, do parse
1421
1422 // what field are we currently in? The first 2 are fixed and there may
1423 // be an arbitrary number of other fields -- currently, we are not
1424 // interested in any of them, but we should parse them as well...
1425 enum
1426 {
1427 Field_Type,
1428 Field_OpenCmd,
1429 Field_Other
1430 } currentToken = Field_Type;
1431
1432 // the flags and field values on the current line
8fc613f1
RR
1433 bool needsterminal = FALSE,
1434 copiousoutput = FALSE;
b13d92d1
VZ
1435 wxString strType,
1436 strOpenCmd,
1437 strPrintCmd,
1438 strTest,
1439 strDesc,
1440 curField; // accumulator
1441 for ( bool cont = TRUE; cont; pc++ ) {
1442 switch ( *pc ) {
223d09f6 1443 case wxT('\\'):
b13d92d1
VZ
1444 // interpret the next character literally (notice that
1445 // backslash can be used for line continuation)
223d09f6 1446 if ( *++pc == wxT('\0') ) {
b13d92d1
VZ
1447 // fetch the next line.
1448
1449 // pc currently points to nowhere, but after the next
1450 // pc++ in the for line it will point to the beginning
1451 // of the next line in the file
1452 pc = file[++nLine].c_str() - 1;
1453 }
1454 else {
1455 // just a normal character
1456 curField += *pc;
1457 }
1458 break;
1459
223d09f6 1460 case wxT('\0'):
b13d92d1
VZ
1461 cont = FALSE; // end of line reached, exit the loop
1462
1463 // fall through
1464
223d09f6 1465 case wxT(';'):
b13d92d1
VZ
1466 // store this field and start looking for the next one
1467
1468 // trim whitespaces from both sides
1469 curField.Trim(TRUE).Trim(FALSE);
1470
1471 switch ( currentToken ) {
1472 case Field_Type:
1473 strType = curField;
223d09f6 1474 if ( strType.Find(wxT('/')) == wxNOT_FOUND ) {
b13d92d1 1475 // we interpret "type" as "type/*"
223d09f6 1476 strType += wxT("/*");
b13d92d1
VZ
1477 }
1478
1479 currentToken = Field_OpenCmd;
1480 break;
1481
1482 case Field_OpenCmd:
1483 strOpenCmd = curField;
1484
1485 currentToken = Field_Other;
1486 break;
1487
1488 case Field_Other:
1489 {
1490 // "good" mailcap entry?
1491 bool ok = TRUE;
1492
1493 // is this something of the form foo=bar?
223d09f6 1494 const wxChar *pEq = wxStrchr(curField, wxT('='));
b13d92d1 1495 if ( pEq != NULL ) {
223d09f6
KB
1496 wxString lhs = curField.BeforeFirst(wxT('=')),
1497 rhs = curField.AfterFirst(wxT('='));
b13d92d1
VZ
1498
1499 lhs.Trim(TRUE); // from right
1500 rhs.Trim(FALSE); // from left
1501
223d09f6 1502 if ( lhs == wxT("print") )
b13d92d1 1503 strPrintCmd = rhs;
223d09f6 1504 else if ( lhs == wxT("test") )
b13d92d1 1505 strTest = rhs;
223d09f6 1506 else if ( lhs == wxT("description") ) {
b13d92d1 1507 // it might be quoted
223d09f6
KB
1508 if ( rhs[0u] == wxT('"') &&
1509 rhs.Last() == wxT('"') ) {
b13d92d1
VZ
1510 strDesc = wxString(rhs.c_str() + 1,
1511 rhs.Len() - 2);
1512 }
1513 else {
1514 strDesc = rhs;
1515 }
1516 }
223d09f6
KB
1517 else if ( lhs == wxT("compose") ||
1518 lhs == wxT("composetyped") ||
1519 lhs == wxT("notes") ||
1520 lhs == wxT("edit") )
b13d92d1
VZ
1521 ; // ignore
1522 else
1523 ok = FALSE;
1524
1525 }
1526 else {
1527 // no, it's a simple flag
1528 // TODO support the flags:
1529 // 1. create an xterm for 'needsterminal'
1530 // 2. append "| $PAGER" for 'copiousoutput'
223d09f6 1531 if ( curField == wxT("needsterminal") )
b13d92d1 1532 needsterminal = TRUE;
223d09f6 1533 else if ( curField == wxT("copiousoutput") )
b13d92d1 1534 copiousoutput = TRUE;
223d09f6 1535 else if ( curField == wxT("textualnewlines") )
b13d92d1
VZ
1536 ; // ignore
1537 else
1538 ok = FALSE;
1539 }
1540
1541 if ( !ok )
1542 {
f6bcfd97 1543 if ( !IsKnownUnimportantField(curField) )
54acce90
VZ
1544 {
1545 // don't flood the user with error
1546 // messages if we don't understand
1547 // something in his mailcap, but give
1548 // them in debug mode because this might
1549 // be useful for the programmer
1550 wxLogDebug
1551 (
1552 wxT("Mailcap file %s, line %d: "
1553 "unknown field '%s' for the "
1554 "MIME type '%s' ignored."),
1555 strFileName.c_str(),
1556 nLine + 1,
1557 curField.c_str(),
1558 strType.c_str()
1559 );
1560 }
b13d92d1
VZ
1561 }
1562 }
1563
1564 // it already has this value
1565 //currentToken = Field_Other;
1566 break;
1567
1568 default:
223d09f6 1569 wxFAIL_MSG(wxT("unknown field type in mailcap"));
b13d92d1
VZ
1570 }
1571
1572 // next token starts immediately after ';'
1573 curField.Empty();
1574 break;
1575
1576 default:
1577 curField += *pc;
1578 }
1579 }
1580
1581 // check that we really read something reasonable
1582 if ( currentToken == Field_Type || currentToken == Field_OpenCmd ) {
1583 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1584 "ignored."),
1585 strFileName.c_str(), nLine + 1);
1586 }
1587 else {
1588 MailCapEntry *entry = new MailCapEntry(strOpenCmd,
1589 strPrintCmd,
1590 strTest);
1591
8e124873
VZ
1592 // NB: because of complications below (we must get entries priority
1593 // right), we can't use AddMailcapInfo() here, unfortunately.
b13d92d1
VZ
1594 strType.MakeLower();
1595 int nIndex = m_aTypes.Index(strType);
3c67202d 1596 if ( nIndex == wxNOT_FOUND ) {
b13d92d1
VZ
1597 // new file type
1598 m_aTypes.Add(strType);
1599
1600 m_aEntries.Add(entry);
223d09f6 1601 m_aExtensions.Add(wxT(""));
b13d92d1
VZ
1602 m_aDescriptions.Add(strDesc);
1603 }
1604 else {
cc385968
VZ
1605 // modify the existing entry: the entries in one and the same
1606 // file are read in top-to-bottom order, i.e. the entries read
1607 // first should be tried before the entries below. However,
1608 // the files read later should override the settings in the
1609 // files read before (except if fallback is TRUE), thus we
1610 // Insert() the new entry to the list if it has already
1611 // occured in _this_ file, but Prepend() it if it occured in
1612 // some of the previous ones and Append() to it in the
1613 // fallback case
1614
1615 if ( fallback ) {
1616 // 'fallback' parameter prevents the entries from this
1617 // file from overriding the other ones - always append
1618 MailCapEntry *entryOld = m_aEntries[nIndex];
1619 if ( entryOld )
1620 entry->Append(entryOld);
1621 else
1622 m_aEntries[nIndex] = entry;
b13d92d1
VZ
1623 }
1624 else {
cc385968
VZ
1625 int entryIndex = aEntryIndices.Index(nIndex);
1626 if ( entryIndex == wxNOT_FOUND ) {
1627 // first time in this file
1628 aEntryIndices.Add(nIndex);
1629 aLastIndices.Add(0);
1630
1631 entry->Prepend(m_aEntries[nIndex]);
1632 m_aEntries[nIndex] = entry;
1633 }
1634 else {
1635 // not the first time in _this_ file
1636 size_t nEntryIndex = (size_t)entryIndex;
1637 MailCapEntry *entryOld = m_aEntries[nIndex];
1638 if ( entryOld )
1639 entry->Insert(entryOld, aLastIndices[nEntryIndex]);
1640 else
1641 m_aEntries[nIndex] = entry;
1642
1643 // the indices were shifted by 1
1644 aLastIndices[nEntryIndex]++;
1645 }
b13d92d1
VZ
1646 }
1647
1648 if ( !strDesc.IsEmpty() ) {
cc385968 1649 // replace the old one - what else can we do??
b13d92d1
VZ
1650 m_aDescriptions[nIndex] = strDesc;
1651 }
1652 }
1653 }
1654
1655 // check our data integriry
1656 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1657 m_aTypes.Count() == m_aExtensions.Count() &&
1658 m_aTypes.Count() == m_aDescriptions.Count() );
1659 }
cc385968
VZ
1660
1661 return TRUE;
b13d92d1
VZ
1662}
1663
696e1ea0 1664size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1b986aef 1665{
54acce90
VZ
1666 mimetypes.Empty();
1667
1668 wxString type;
1669 size_t count = m_aTypes.GetCount();
1670 for ( size_t n = 0; n < count; n++ )
1671 {
1672 // don't return template types from here (i.e. anything containg '*')
1673 type = m_aTypes[n];
1674 if ( type.Find(_T('*')) == wxNOT_FOUND )
1675 {
1676 mimetypes.Add(type);
1677 }
1678 }
1b986aef 1679
54acce90 1680 return mimetypes.GetCount();
1b986aef
VZ
1681}
1682
f6bcfd97
BP
1683// ----------------------------------------------------------------------------
1684// private functions
1685// ----------------------------------------------------------------------------
1686
1687static bool IsKnownUnimportantField(const wxString& fieldAll)
1688{
1689 static const wxChar *knownFields[] =
1690 {
1691 _T("x-mozilla-flags"),
1692 _T("nametemplate"),
1693 _T("textualnewlines"),
1694 };
1695
1696 wxString field = fieldAll.BeforeFirst(_T('='));
1697 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
1698 {
1699 if ( field.CmpNoCase(knownFields[n]) == 0 )
1700 return TRUE;
1701 }
1702
1703 return FALSE;
1704}
1705
8e124873 1706#endif
ce4169a4 1707 // wxUSE_FILE && wxUSE_TEXTFILE
b13d92d1 1708