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