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