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