]> git.saurik.com Git - wxWidgets.git/blame - src/unix/mimetype.cpp
Corrected mime types docs
[wxWidgets.git] / src / unix / mimetype.cpp
CommitLineData
b13d92d1 1/////////////////////////////////////////////////////////////////////////////
7520f3da 2// Name: src/unix/mimetype.cpp
b13d92d1
VZ
3// Purpose: classes and functions to manage MIME types
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 23.09.98
7// RCS-ID: $Id$
8// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
65571936 9// Licence: wxWindows licence (part of wxExtra library)
b13d92d1
VZ
10/////////////////////////////////////////////////////////////////////////////
11
b13d92d1
VZ
12// for compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
15#ifdef __BORLANDC__
7520f3da 16 #pragma hdrstop
ce4169a4
RR
17#endif
18
b79e32cc 19#if wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE
ce4169a4 20
88a7a4e1
WS
21#include "wx/unix/mimetype.h"
22
ce4169a4 23#ifndef WX_PRECOMP
ad9835c9 24 #include "wx/dynarray.h"
7520f3da 25 #include "wx/string.h"
88a7a4e1 26 #include "wx/intl.h"
e4db172a 27 #include "wx/log.h"
de6185e2 28 #include "wx/utils.h"
7c2e5dec 29#endif
3d05544e 30
ce4169a4 31#include "wx/file.h"
2432b92d 32#include "wx/confbase.h"
b13d92d1 33
7dc3cc31
VS
34#include "wx/ffile.h"
35#include "wx/textfile.h"
36#include "wx/dir.h"
7dc3cc31 37#include "wx/tokenzr.h"
c41dbc20 38#include "wx/iconloc.h"
1d529ef7 39#include "wx/filename.h"
88bbc332
RR
40#include "wx/app.h"
41#include "wx/apptrait.h"
b13d92d1 42
b13d92d1
VZ
43// other standard headers
44#include <ctype.h>
45
29886d1b 46class wxMimeTextFile
2b813b73
VZ
47{
48public:
29886d1b
RR
49 wxMimeTextFile()
50 {
51 }
52
53 wxMimeTextFile(const wxString& fname)
54 {
55 m_fname = fname;
56 }
57
58 void AddLine( const wxString &line ) { }
59
60 bool Open( const wxString &fname )
61 {
62 m_fname = fname;
63 wxFFile file( fname );
64 if (!file.IsOpened())
65 return false;
66 size_t size = file.Length();
67 wxCharBuffer buffer( size );
68 file.Read( (void*) (const char*) buffer, size );
69 wxString all = wxString::FromUTF8( buffer, size );
70
71 wxStringTokenizer tok( all, "\n" );
72 while (tok.HasMoreTokens())
73 {
74 wxString t = tok.GetNextToken();
75 t.MakeLower();
76 if ((!!t) && (t.Find( "comment" ) != 0) && (t.Find( "generic" ) != 0))
77 m_text.Add( t );
78 }
79 return true;
80 }
81 bool Open() { return Open(m_fname); }
82 bool Create( const wxString &fname ) { return false; }
83 bool Write() { return false; }
84 bool Close() { return false; }
85
86 unsigned int GetLineCount() const { return m_text.GetCount(); }
87 wxString &GetLine( unsigned int line ) { return m_text[line]; }
2b813b73 88
d9bbb2d8
VZ
89 int pIndexOf(const wxString& sSearch,
90 bool bIncludeComments = false,
91 int iStart = 0)
2b813b73 92 {
2b813b73
VZ
93 wxString sTest = sSearch;
94 sTest.MakeLower();
31383ebc 95 for(size_t i = iStart; i < GetLineCount(); i++)
2b813b73 96 {
29886d1b 97 wxString sLine = GetLine(i);
31383ebc 98 if(bIncludeComments || ! sLine.StartsWith(wxT("#")))
2b813b73 99 {
31383ebc
RR
100 if(sLine.StartsWith(sTest))
101 return (int)i;
2b813b73
VZ
102 }
103 }
31383ebc 104 return wxNOT_FOUND;
2b813b73
VZ
105 }
106
107 bool CommentLine(int nIndex)
108 {
dfea7acc
DS
109 if (nIndex < 0)
110 return false;
111 if (nIndex >= (int)GetLineCount() )
112 return false;
7c2e5dec 113
2b813b73 114 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
d0ee33f5 115 return true;
2b813b73
VZ
116 }
117
118 bool CommentLine(const wxString & sTest)
119 {
120 int nIndex = pIndexOf(sTest);
dfea7acc
DS
121 if (nIndex < 0)
122 return false;
123 if (nIndex >= (int)GetLineCount() )
124 return false;
125
2b813b73 126 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
d0ee33f5 127 return true;
2b813b73
VZ
128 }
129
dfea7acc 130 wxString GetVerb(size_t i)
2b813b73 131 {
dfea7acc
DS
132 if (i > GetLineCount() )
133 return wxEmptyString;
134
2b813b73
VZ
135 wxString sTmp = GetLine(i).BeforeFirst(wxT('='));
136 return sTmp;
137 }
138
dfea7acc 139 wxString GetCmd(size_t i)
2b813b73 140 {
dfea7acc
DS
141 if (i > GetLineCount() )
142 return wxEmptyString;
143
2b813b73
VZ
144 wxString sTmp = GetLine(i).AfterFirst(wxT('='));
145 return sTmp;
146 }
29886d1b
RR
147
148private:
149 wxArrayString m_text;
150 wxString m_fname;
2b813b73
VZ
151};
152
f6bcfd97
BP
153// ----------------------------------------------------------------------------
154// constants
155// ----------------------------------------------------------------------------
156
157// MIME code tracing mask
dfea7acc 158#define TRACE_MIME wxT("mime")
f6bcfd97 159
678ebfcd 160// give trace messages about the results of mailcap tests
dfea7acc 161#define TRACE_MIME_TEST wxT("mimetest")
678ebfcd 162
f6bcfd97 163// ----------------------------------------------------------------------------
4d2976ad 164
b9517a0a 165// ----------------------------------------------------------------------------
678ebfcd 166// KDE
b9517a0a
VZ
167// ----------------------------------------------------------------------------
168
678ebfcd 169
b9517a0a
VZ
170// KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
171// may be found in either of the following locations
172//
509a6196 173// 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
b9517a0a
VZ
174// 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
175//
176// The format of a .kdelnk file is almost the same as the one used by
177// wxFileConfig, i.e. there are groups, comments and entries. The icon is the
178// value for the entry "Type"
179
2b813b73
VZ
180// kde writing; see http://webcvs.kde.org/cgi-bin/cvsweb.cgi/~checkout~/kdelibs/kio/DESKTOP_ENTRY_STANDARD
181// for now write to .kdelnk but should eventually do .desktop instead (in preference??)
182
dfea7acc 183bool wxMimeTypesManagerImpl::CheckKDEDirsExist( const wxString &sOK, const wxString &sTest )
10274336 184{
678ebfcd 185 if (sTest.empty())
10274336 186 {
dfea7acc 187 return wxDir::Exists(sOK);
10274336 188 }
2b813b73 189 else
10274336
RR
190 {
191 wxString sStart = sOK + wxT("/") + sTest.BeforeFirst(wxT('/'));
dfea7acc
DS
192 if (!wxDir::Exists(sStart))
193 wxMkdir(sStart);
10274336
RR
194 wxString sEnd = sTest.AfterFirst(wxT('/'));
195 return CheckKDEDirsExist(sStart, sEnd);
2b813b73
VZ
196 }
197}
198
199bool wxMimeTypesManagerImpl::WriteKDEMimeFile(int index, bool delete_index)
200{
201 wxMimeTextFile appoutfile, mimeoutfile;
202 wxString sHome = wxGetHomeDir();
203 wxString sTmp = wxT(".kde/share/mimelnk/");
678ebfcd 204 wxString sMime = m_aTypes[index];
dfea7acc 205 CheckKDEDirsExist(sHome, sTmp + sMime.BeforeFirst(wxT('/')) );
2b813b73
VZ
206 sTmp = sHome + wxT('/') + sTmp + sMime + wxT(".kdelnk");
207
208 bool bTemp;
dfea7acc 209 bool bMimeExists = mimeoutfile.Open(sTmp);
2b813b73
VZ
210 if (!bMimeExists)
211 {
dfea7acc 212 bTemp = mimeoutfile.Create(sTmp);
678ebfcd 213 // some unknown error eg out of disk space
dfea7acc
DS
214 if (!bTemp)
215 return false;
2b813b73
VZ
216 }
217
218 sTmp = wxT(".kde/share/applnk/");
dfea7acc 219 CheckKDEDirsExist(sHome, sTmp + sMime.AfterFirst(wxT('/')) );
2b813b73
VZ
220 sTmp = sHome + wxT('/') + sTmp + sMime.AfterFirst(wxT('/')) + wxT(".kdelnk");
221
222 bool bAppExists;
dfea7acc 223 bAppExists = appoutfile.Open(sTmp);
2b813b73
VZ
224 if (!bAppExists)
225 {
dfea7acc 226 bTemp = appoutfile.Create(sTmp);
2b813b73 227 // some unknown error eg out of disk space
dfea7acc
DS
228 if (!bTemp)
229 return false;
2b813b73
VZ
230 }
231
232 // fixed data; write if new file
233 if (!bMimeExists)
234 {
235 mimeoutfile.AddLine(wxT("#KDE Config File"));
236 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
237 mimeoutfile.AddLine(wxT("Version=1.0"));
238 mimeoutfile.AddLine(wxT("Type=MimeType"));
239 mimeoutfile.AddLine(wxT("MimeType=") + sMime);
240 }
241
242 if (!bAppExists)
243 {
244 mimeoutfile.AddLine(wxT("#KDE Config File"));
245 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
246 appoutfile.AddLine(wxT("Version=1.0"));
247 appoutfile.AddLine(wxT("Type=Application"));
248 appoutfile.AddLine(wxT("MimeType=") + sMime + wxT(';'));
249 }
250
251 // variable data
252 // ignore locale
253 mimeoutfile.CommentLine(wxT("Comment="));
254 if (!delete_index)
255 mimeoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
256 appoutfile.CommentLine(wxT("Name="));
257 if (!delete_index)
258 appoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
259
260 sTmp = m_aIcons[index];
261 // we can either give the full path, or the shortfilename if its in
262 // one of the directories we search
263 mimeoutfile.CommentLine(wxT("Icon=") );
dfea7acc
DS
264 if (!delete_index)
265 mimeoutfile.AddLine(wxT("Icon=") + sTmp );
7c2e5dec 266 appoutfile.CommentLine(wxT("Icon=") );
dfea7acc
DS
267 if (!delete_index)
268 appoutfile.AddLine(wxT("Icon=") + sTmp );
2b813b73
VZ
269
270 sTmp = wxT(" ") + m_aExtensions[index];
271
dfea7acc 272 wxStringTokenizer tokenizer(sTmp, wxT(" "));
2b813b73
VZ
273 sTmp = wxT("Patterns=");
274 mimeoutfile.CommentLine(sTmp);
275 while ( tokenizer.HasMoreTokens() )
276 {
277 // holds an extension; need to change it to *.ext;
278 wxString e = wxT("*.") + tokenizer.GetNextToken() + wxT(";");
15d4b8cd 279 sTmp += e;
678ebfcd 280 }
dfea7acc
DS
281
282 if (!delete_index)
283 mimeoutfile.AddLine(sTmp);
2b813b73 284
678ebfcd 285 wxMimeTypeCommands * entries = m_aEntries[index];
2b813b73 286 // if we don't find open just have an empty string ... FIX this
dfea7acc 287 sTmp = entries->GetCommandForVerb(wxT("open"));
2b813b73
VZ
288 sTmp.Replace( wxT("%s"), wxT("%f") );
289
290 mimeoutfile.CommentLine(wxT("DefaultApp=") );
dfea7acc
DS
291 if (!delete_index)
292 mimeoutfile.AddLine(wxT("DefaultApp=") + sTmp);
2b813b73
VZ
293
294 sTmp.Replace( wxT("%f"), wxT("") );
295 appoutfile.CommentLine(wxT("Exec="));
dfea7acc
DS
296 if (!delete_index)
297 appoutfile.AddLine(wxT("Exec=") + sTmp);
2b813b73
VZ
298
299 if (entries->GetCount() > 1)
678ebfcd
VZ
300 {
301 //other actions as well as open
678ebfcd 302 }
dfea7acc 303
d0ee33f5 304 bTemp = false;
dfea7acc
DS
305 if (mimeoutfile.Write())
306 bTemp = true;
307 mimeoutfile.Close();
308 if (appoutfile.Write())
309 bTemp = true;
310 appoutfile.Close();
2b813b73
VZ
311
312 return bTemp;
2b813b73
VZ
313}
314
31383ebc
RR
315// Read a KDE .desktop file of type 'Application'
316void wxMimeTypesManagerImpl::LoadKDEApp(const wxString& filename)
b9517a0a 317{
31383ebc 318 wxLogTrace(TRACE_MIME, wxT("loading KDE file %s"), filename.c_str());
320c341a 319
d9bbb2d8
VZ
320 wxMimeTextFile file;
321 if ( !file.Open(filename) )
322 return;
29886d1b 323
31383ebc 324 // Here, only type 'Application' should be considered.
29886d1b
RR
325 int nIndex = file.pIndexOf( "Type=" );
326 if (nIndex != wxNOT_FOUND && file.GetCmd(nIndex) != "application")
31383ebc 327 return;
dfea7acc 328
31383ebc 329 // The hidden entry specifies a file to be ignored.
29886d1b
RR
330 nIndex = file.pIndexOf( "Hidden=" );
331 if (nIndex != wxNOT_FOUND && file.GetCmd(nIndex) == "true")
31383ebc 332 return;
dfea7acc 333
31383ebc
RR
334 // Semicolon separated list of mime types handled by the application.
335 nIndex = file.pIndexOf( wxT("MimeType=") );
336 if (nIndex == wxNOT_FOUND)
337 return;
338 wxString mimetypes = file.GetCmd (nIndex);
dfea7acc 339
31383ebc
RR
340 // Name of the application
341 wxString nameapp;
342 nIndex = wxNOT_FOUND;
343#if wxUSE_INTL // try "Name[locale name]" first
344 wxLocale *locale = wxGetLocale();
345 if ( locale )
346 nIndex = file.pIndexOf(_T("Name[")+locale->GetName()+_T("]="));
347#endif // wxUSE_INTL
348 if(nIndex == wxNOT_FOUND)
349 nIndex = file.pIndexOf( wxT("Name=") );
350 if(nIndex != wxNOT_FOUND)
351 nameapp = file.GetCmd(nIndex);
352
353 // Icon of the application.
354 wxString nameicon, namemini;
355 nIndex = wxNOT_FOUND;
356#if wxUSE_INTL // try "Icon[locale name]" first
357 if ( locale )
358 nIndex = file.pIndexOf(_T("Icon[")+locale->GetName()+_T("]="));
359#endif // wxUSE_INTL
360 if(nIndex == wxNOT_FOUND)
361 nIndex = file.pIndexOf( wxT("Icon=") );
362 if(nIndex != wxNOT_FOUND) {
363 nameicon = wxString(wxT("--icon ")) + file.GetCmd(nIndex);
364 namemini = wxString(wxT("--miniicon ")) + file.GetCmd(nIndex);
365 }
366
367 // Replace some of the field code in the 'Exec' entry.
368 // TODO: deal with %d, %D, %n, %N, %k and %v (but last one is deprecated)
369 nIndex = file.pIndexOf( wxT("Exec=") );
370 if (nIndex == wxNOT_FOUND)
371 return;
372 wxString sCmd = file.GetCmd(nIndex);
373 // we expect %f; others including %F and %U and %u are possible
374 sCmd.Replace(wxT("%F"), wxT("%f"));
375 sCmd.Replace(wxT("%U"), wxT("%f"));
376 sCmd.Replace(wxT("%u"), wxT("%f"));
377 if (0 == sCmd.Replace ( wxT("%f"), wxT("%s") ))
378 sCmd = sCmd + wxT(" %s");
379 sCmd.Replace(wxT("%c"), nameapp);
380 sCmd.Replace(wxT("%i"), nameicon);
381 sCmd.Replace(wxT("%m"), namemini);
382
383 wxStringTokenizer tokenizer(mimetypes, _T(";"));
384 while(tokenizer.HasMoreTokens()) {
385 wxString mimetype = tokenizer.GetNextToken().Lower();
309aefbd 386 nIndex = m_aTypes.Index(mimetype);
31383ebc
RR
387 if(nIndex != wxNOT_FOUND) { // is this a known MIME type?
388 wxMimeTypeCommands* entry = m_aEntries[nIndex];
389 entry->AddOrReplaceVerb(wxT("open"), sCmd);
10274336 390 }
31383ebc
RR
391 }
392}
d0ee33f5 393
31383ebc
RR
394void wxMimeTypesManagerImpl::LoadKDEAppsFilesFromDir(const wxString& dirname)
395{
6fc93e9b
JS
396 // Don't complain if we don't have permissions to read - it confuses users
397 wxLogNull logNull;
398
31383ebc
RR
399 if(! wxDir::Exists(dirname))
400 return;
401 wxDir dir(dirname);
402 if ( !dir.IsOpened() )
403 return;
d0ee33f5 404
31383ebc
RR
405 wxString filename;
406 // Look into .desktop files
407 bool cont = dir.GetFirst(&filename, _T("*.desktop"), wxDIR_FILES);
29886d1b
RR
408 while (cont)
409 {
31383ebc
RR
410 wxFileName p(dirname, filename);
411 LoadKDEApp( p.GetFullPath() );
412 cont = dir.GetNext(&filename);
413 }
29886d1b
RR
414
415 return;
416
31383ebc
RR
417 // Look recursively into subdirs
418 cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
29886d1b
RR
419 while (cont)
420 {
31383ebc
RR
421 wxFileName p(dirname, wxEmptyString);
422 p.AppendDir(filename);
423 LoadKDEAppsFilesFromDir( p.GetPath() );
424 cont = dir.GetNext(&filename);
425 }
426}
d0ee33f5 427
b9517a0a 428
2b813b73
VZ
429// ----------------------------------------------------------------------------
430// wxFileTypeImpl (Unix)
431// ----------------------------------------------------------------------------
432
2b813b73 433wxString wxFileTypeImpl::GetExpandedCommand(const wxString & verb, const wxFileType::MessageParameters& params) const
b9517a0a 434{
2b813b73
VZ
435 wxString sTmp;
436 size_t i = 0;
678ebfcd 437 while ( (i < m_index.GetCount() ) && sTmp.empty() )
b9517a0a 438 {
dfea7acc
DS
439 sTmp = m_manager->GetCommand( verb, m_index[i] );
440 i++;
b9517a0a
VZ
441 }
442
2b813b73
VZ
443 return wxFileType::ExpandCommand(sTmp, params);
444}
b9517a0a 445
da0766ab 446bool wxFileTypeImpl::GetIcon(wxIconLocation *iconLoc) const
2b813b73
VZ
447{
448 wxString sTmp;
449 size_t i = 0;
678ebfcd 450 while ( (i < m_index.GetCount() ) && sTmp.empty() )
c786f763
VZ
451 {
452 sTmp = m_manager->m_aIcons[m_index[i]];
dfea7acc 453 i++;
c786f763 454 }
dfea7acc
DS
455
456 if ( sTmp.empty() )
d0ee33f5 457 return false;
b9517a0a 458
da0766ab 459 if ( iconLoc )
c786f763 460 {
a49686b4 461 iconLoc->SetFileName(sTmp);
2b813b73 462 }
c786f763 463
d0ee33f5 464 return true;
c786f763 465}
2b813b73 466
dfea7acc 467bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
2b813b73
VZ
468{
469 mimeTypes.Clear();
15d4b8cd
RR
470 size_t nCount = m_index.GetCount();
471 for (size_t i = 0; i < nCount; i++)
2b813b73 472 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
dfea7acc 473
d0ee33f5 474 return true;
2b813b73
VZ
475}
476
2b813b73
VZ
477size_t wxFileTypeImpl::GetAllCommands(wxArrayString *verbs,
478 wxArrayString *commands,
479 const wxFileType::MessageParameters& params) const
480{
2b813b73
VZ
481 wxString vrb, cmd, sTmp;
482 size_t count = 0;
678ebfcd 483 wxMimeTypeCommands * sPairs;
2b813b73
VZ
484
485 // verbs and commands have been cleared already in mimecmn.cpp...
486 // if we find no entries in the exact match, try the inexact match
7c2e5dec 487 for (size_t n = 0; ((count == 0) && (n < m_index.GetCount())); n++)
2b813b73
VZ
488 {
489 // list of verb = command pairs for this mimetype
490 sPairs = m_manager->m_aEntries [m_index[n]];
491 size_t i;
dfea7acc
DS
492 for ( i = 0; i < sPairs->GetCount(); i++ )
493 {
494 vrb = sPairs->GetVerb(i);
7c2e5dec 495 // some gnome entries have "." inside
dfea7acc
DS
496 vrb = vrb.AfterLast(wxT('.'));
497 cmd = sPairs->GetCmd(i);
498 if (! cmd.empty() )
2b813b73 499 {
dfea7acc 500 cmd = wxFileType::ExpandCommand(cmd, params);
7c2e5dec 501 count++;
dfea7acc
DS
502 if ( vrb.IsSameAs(wxT("open")))
503 {
d6a7ca31
VZ
504 if ( verbs )
505 verbs->Insert(vrb, 0u);
506 if ( commands )
507 commands ->Insert(cmd, 0u);
dfea7acc
DS
508 }
509 else
510 {
d6a7ca31
VZ
511 if ( verbs )
512 verbs->Add(vrb);
513 if ( commands )
514 commands->Add(cmd);
dfea7acc
DS
515 }
516 }
2b813b73 517 }
2b813b73 518 }
2b813b73 519
dfea7acc 520 return count;
2b813b73
VZ
521}
522
523bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
524{
86501081 525 const wxString strExtensions = m_manager->GetExtension(m_index[0]);
2b813b73
VZ
526 extensions.Empty();
527
1c4cd9e0 528 // one extension in the space or comma-delimited list
2b813b73 529 wxString strExt;
86501081
VS
530 wxString::const_iterator end = strExtensions.end();
531 for ( wxString::const_iterator p = strExtensions.begin(); /* nothing */; ++p )
dfea7acc 532 {
86501081 533 if ( p == end || *p == wxT(' ') || *p == wxT(',') )
dfea7acc
DS
534 {
535 if ( !strExt.empty() )
536 {
2b813b73
VZ
537 extensions.Add(strExt);
538 strExt.Empty();
539 }
7c2e5dec
DS
540 //else: repeated spaces
541 // (shouldn't happen, but it's not that important if it does happen)
2b813b73 542
86501081 543 if ( p == end )
2b813b73
VZ
544 break;
545 }
dfea7acc
DS
546 else if ( *p == wxT('.') )
547 {
2b813b73 548 // remove the dot from extension (but only if it's the first char)
dfea7acc
DS
549 if ( !strExt.empty() )
550 {
2b813b73
VZ
551 strExt += wxT('.');
552 }
553 //else: no, don't append it
554 }
dfea7acc
DS
555 else
556 {
2b813b73
VZ
557 strExt += *p;
558 }
559 }
560
d0ee33f5 561 return true;
2b813b73
VZ
562}
563
7c2e5dec 564// set an arbitrary command:
2b813b73 565// could adjust the code to ask confirmation if it already exists and
d0ee33f5 566// overwriteprompt is true, but this is currently ignored as *Associate* has
2b813b73 567// no overwrite prompt
17a1ebd1
VZ
568bool
569wxFileTypeImpl::SetCommand(const wxString& cmd,
570 const wxString& verb,
571 bool WXUNUSED(overwriteprompt))
d84afea9 572{
2b813b73 573 wxArrayString strExtensions;
678ebfcd 574 wxString strDesc, strIcon;
2b813b73 575
2b813b73 576 wxArrayString strTypes;
dfea7acc 577 GetMimeTypes(strTypes);
926ce9e3 578 if ( strTypes.IsEmpty() )
dfea7acc 579 return false;
2b813b73 580
926ce9e3
VZ
581 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
582 entry->Add(verb + wxT("=") + cmd + wxT(" %s "));
583
dda522bf 584 bool ok = false;
15d4b8cd
RR
585 size_t nCount = strTypes.GetCount();
586 for ( size_t i = 0; i < nCount; i++ )
d84afea9 587 {
dda522bf
VZ
588 if ( m_manager->DoAssociation
589 (
590 strTypes[i],
591 strIcon,
592 entry,
593 strExtensions,
594 strDesc
595 ) )
596 {
597 // DoAssociation() took ownership of entry, don't delete it below
598 ok = true;
599 }
d84afea9 600 }
2b813b73 601
dda522bf
VZ
602 if ( !ok )
603 delete entry;
604
dfea7acc 605 return ok;
d84afea9 606}
2b813b73 607
15d4b8cd 608// ignore index on the grounds that we only have one icon in a Unix file
17a1ebd1 609bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int WXUNUSED(index))
d84afea9 610{
dfea7acc
DS
611 if (strIcon.empty())
612 return false;
613
2b813b73
VZ
614 wxArrayString strExtensions;
615 wxString strDesc;
616
2b813b73 617 wxArrayString strTypes;
dfea7acc 618 GetMimeTypes(strTypes);
cb0b7b7d 619 if ( strTypes.IsEmpty() )
dfea7acc 620 return false;
2b813b73 621
cb0b7b7d 622 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
2f769cb9 623 bool ok = false;
15d4b8cd
RR
624 size_t nCount = strTypes.GetCount();
625 for ( size_t i = 0; i < nCount; i++ )
d84afea9 626 {
2f769cb9
VZ
627 if ( m_manager->DoAssociation
628 (
cb0b7b7d
VZ
629 strTypes[i],
630 strIcon,
631 entry,
632 strExtensions,
633 strDesc
2f769cb9 634 ) )
cb0b7b7d 635 {
2f769cb9
VZ
636 // we don't need to free entry now, DoAssociation() took ownership
637 // of it
638 ok = true;
cb0b7b7d 639 }
d84afea9 640 }
2b813b73 641
2f769cb9
VZ
642 if ( !ok )
643 delete entry;
644
dfea7acc 645 return ok;
d84afea9
GD
646}
647
2b813b73
VZ
648// ----------------------------------------------------------------------------
649// wxMimeTypesManagerImpl (Unix)
650// ----------------------------------------------------------------------------
651
2b813b73
VZ
652wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
653{
d0ee33f5 654 m_initialized = false;
2b813b73
VZ
655}
656
1d529ef7
RR
657void wxMimeTypesManagerImpl::InitIfNeeded()
658{
659 if ( !m_initialized )
660 {
661 // set the flag first to prevent recursion
d0ee33f5 662 m_initialized = true;
88bbc332
RR
663
664 wxString wm = wxTheApp->GetTraits()->GetDesktopEnvironment();
665
666 if (wm == wxT("KDE"))
667 Initialize( wxMAILCAP_KDE );
668 else if (wm == wxT("GNOME"))
669 Initialize( wxMAILCAP_GNOME );
670 else
671 Initialize();
1d529ef7
RR
672 }
673}
674
2b813b73
VZ
675// read system and user mailcaps and other files
676void wxMimeTypesManagerImpl::Initialize(int mailcapStyles,
677 const wxString& sExtraDir)
678{
29886d1b
RR
679#ifdef __VMS
680 // XDG tables are never installed on OpenVMS
681 return;
7538fdc9 682#endif
2b813b73 683
84513dcb
JS
684 // Load desktop files for Gnome, and then override them with the Gnome defaults.
685 // We will override them one desktop file at a time, rather
686 // than one mime type at a time, but it should be a reasonable
687 // heuristic.
84513dcb
JS
688 {
689 wxString xdgDataHome = wxGetenv(wxT("XDG_DATA_HOME"));
690 if ( xdgDataHome.empty() )
691 xdgDataHome = wxGetHomeDir() + wxT("/.local/share");
692 wxString xdgDataDirs = wxGetenv(wxT("XDG_DATA_DIRS"));
693 if ( xdgDataDirs.empty() )
694 xdgDataDirs = wxT("/usr/local/share:/usr/share:/usr/share/gnome");
695 wxArrayString dirs;
696
697 wxStringTokenizer tokenizer(xdgDataDirs, wxT(":"));
698 while ( tokenizer.HasMoreTokens() )
699 {
700 wxString p = tokenizer.GetNextToken();
701 dirs.Add(p);
702 }
703 dirs.insert(dirs.begin(), xdgDataHome);
704
705 wxString defaultsList;
706 size_t i;
707 for (i = 0; i < dirs.GetCount(); i++)
708 {
29886d1b
RR
709 wxString f = dirs[i];
710 if (f.Last() != '/') f += '/';
711 f += "applications/defaults.list";
84513dcb
JS
712 if (wxFileExists(f))
713 {
714 defaultsList = f;
715 break;
716 }
717 }
718
719 // Load application files and associate them to corresponding mime types.
720 size_t nDirs = dirs.GetCount();
721 for (size_t nDir = 0; nDir < nDirs; nDir++)
722 {
29886d1b
RR
723 wxString dirStr = dirs[nDir];
724 if (dirStr.Last() != '/') dirStr += '/';
725 dirStr += "applications";
84513dcb
JS
726 LoadKDEAppsFilesFromDir(dirStr);
727 }
728
729 if (!defaultsList.IsEmpty())
730 {
731 wxArrayString deskTopFilesSeen;
732
733 wxMimeTextFile textfile(defaultsList);
734 if ( textfile.Open() )
735 {
736 int nIndex = textfile.pIndexOf( wxT("[Default Applications]") );
737 if (nIndex != wxNOT_FOUND)
738 {
739 for (i = nIndex+1; i < textfile.GetLineCount(); i++)
740 {
29886d1b 741 if (textfile.GetLine(i).Find(wxT("=")) != wxNOT_FOUND)
84513dcb
JS
742 {
743 wxString mimeType = textfile.GetVerb(i);
744 wxString desktopFile = textfile.GetCmd(i);
29886d1b 745
84513dcb
JS
746 if (deskTopFilesSeen.Index(desktopFile) == wxNOT_FOUND)
747 {
748 deskTopFilesSeen.Add(desktopFile);
749 size_t j;
29886d1b
RR
750 for (j = 0; j < dirs.GetCount(); j++)
751 {
752 wxString desktopPath = dirs[j];
753 if (desktopPath.Last() == '/')
754 desktopPath += "applications/";
755 else
756 desktopPath += "/applications/";
757 desktopPath += desktopFile;
758
759 if (wxFileExists(desktopPath))
760 LoadKDEApp(desktopPath);
761 }
762 }
763 }
2b813b73 764 }
29886d1b 765 }
2b813b73 766 }
b13d92d1 767 }
b13d92d1 768 }
29886d1b 769}
dfea7acc 770
29886d1b
RR
771// clear data so you can read another group of WM files
772void wxMimeTypesManagerImpl::ClearData()
773{
774 m_aTypes.Clear();
775 m_aIcons.Clear();
776 m_aExtensions.Clear();
777 m_aDescriptions.Clear();
778
779 WX_CLEAR_ARRAY(m_aEntries);
780 m_aEntries.Empty();
781}
782
783wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
784{
785 ClearData();
b13d92d1
VZ
786}
787
dfea7acc 788wxFileType * wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
b9517a0a 789{
2b813b73 790 InitIfNeeded();
b9517a0a 791
dfea7acc
DS
792 wxString strType = ftInfo.GetMimeType();
793 wxString strDesc = ftInfo.GetDescription();
794 wxString strIcon = ftInfo.GetIconFile();
2b813b73 795
dfea7acc 796 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
2b813b73 797
678ebfcd 798 if ( ! ftInfo.GetOpenCommand().empty())
dfea7acc
DS
799 entry->Add(wxT("open=") + ftInfo.GetOpenCommand() + wxT(" %s "));
800 if ( ! ftInfo.GetPrintCommand().empty())
801 entry->Add(wxT("print=") + ftInfo.GetPrintCommand() + wxT(" %s "));
2b813b73
VZ
802
803 // now find where these extensions are in the data store and remove them
dfea7acc 804 wxArrayString sA_Exts = ftInfo.GetExtensions();
2b813b73
VZ
805 wxString sExt, sExtStore;
806 size_t i, nIndex;
15d4b8cd
RR
807 size_t nExtCount = sA_Exts.GetCount();
808 for (i=0; i < nExtCount; i++)
dfea7acc 809 {
2b813b73 810 sExt = sA_Exts.Item(i);
dfea7acc
DS
811
812 // clean up to just a space before and after
d0ee33f5 813 sExt.Trim().Trim(false);
2b813b73 814 sExt = wxT(' ') + sExt + wxT(' ');
15d4b8cd
RR
815 size_t nCount = m_aExtensions.GetCount();
816 for (nIndex = 0; nIndex < nCount; nIndex++)
dfea7acc 817 {
2b813b73 818 sExtStore = m_aExtensions.Item(nIndex);
dfea7acc
DS
819 if (sExtStore.Replace(sExt, wxT(" ") ) > 0)
820 m_aExtensions.Item(nIndex) = sExtStore;
821 }
2b813b73 822 }
b9517a0a 823
dfea7acc 824 if ( !DoAssociation(strType, strIcon, entry, sA_Exts, strDesc) )
2b813b73 825 return NULL;
4d2976ad 826
2b813b73 827 return GetFileTypeFromMimeType(strType);
4d2976ad
VS
828}
829
2b813b73
VZ
830bool wxMimeTypesManagerImpl::DoAssociation(const wxString& strType,
831 const wxString& strIcon,
678ebfcd 832 wxMimeTypeCommands *entry,
2b813b73
VZ
833 const wxArrayString& strExtensions,
834 const wxString& strDesc)
b13d92d1 835{
d0ee33f5 836 int nIndex = AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
7520f3da 837
2b813b73 838 if ( nIndex == wxNOT_FOUND )
d0ee33f5 839 return false;
b13d92d1 840
dfea7acc 841 return WriteMimeInfo(nIndex, false);
b13d92d1
VZ
842}
843
2b813b73 844bool wxMimeTypesManagerImpl::WriteMimeInfo(int nIndex, bool delete_mime )
b13d92d1 845{
d0ee33f5 846 bool ok = true;
b13d92d1 847
2b850ae1
RR
848 // Don't write GNOME files here as this is not
849 // allowed and simply doesn't work
dfea7acc 850
29886d1b 851 // if (m_mailcapStylesInited & wxMAILCAP_KDE)
2b813b73
VZ
852 {
853 // write in KDE format;
dfea7acc 854 if (WriteKDEMimeFile(nIndex, delete_mime) )
d0ee33f5 855 ok = false;
b9517a0a
VZ
856 }
857
2b813b73 858 return ok;
a6c65e88
VZ
859}
860
2b813b73
VZ
861int wxMimeTypesManagerImpl::AddToMimeData(const wxString& strType,
862 const wxString& strIcon,
678ebfcd 863 wxMimeTypeCommands *entry,
2b813b73
VZ
864 const wxArrayString& strExtensions,
865 const wxString& strDesc,
678ebfcd 866 bool replaceExisting)
b13d92d1 867{
2b813b73 868 InitIfNeeded();
b13d92d1 869
2b813b73 870 // ensure mimetype is always lower case
678ebfcd
VZ
871 wxString mimeType = strType.Lower();
872
873 // is this a known MIME type?
2b813b73
VZ
874 int nIndex = m_aTypes.Index(mimeType);
875 if ( nIndex == wxNOT_FOUND )
876 {
877 // new file type
878 m_aTypes.Add(mimeType);
879 m_aIcons.Add(strIcon);
678ebfcd 880 m_aEntries.Add(entry ? entry : new wxMimeTypeCommands);
b13d92d1 881
678ebfcd 882 // change nIndex so we can use it below to add the extensions
df5168c4 883 m_aExtensions.Add(wxEmptyString);
b8d61021 884 nIndex = m_aExtensions.size() - 1;
678ebfcd
VZ
885
886 m_aDescriptions.Add(strDesc);
b13d92d1 887 }
678ebfcd 888 else // yes, we already have it
2b813b73 889 {
678ebfcd 890 if ( replaceExisting )
2b813b73
VZ
891 {
892 // if new description change it
678ebfcd 893 if ( !strDesc.empty())
2b813b73
VZ
894 m_aDescriptions[nIndex] = strDesc;
895
896 // if new icon change it
678ebfcd 897 if ( !strIcon.empty())
2b813b73
VZ
898 m_aIcons[nIndex] = strIcon;
899
678ebfcd
VZ
900 if ( entry )
901 {
902 delete m_aEntries[nIndex];
903 m_aEntries[nIndex] = entry;
904 }
2b813b73 905 }
678ebfcd 906 else // add data we don't already have ...
2b813b73 907 {
2b813b73 908 // if new description add only if none
678ebfcd 909 if ( m_aDescriptions[nIndex].empty() )
2b813b73
VZ
910 m_aDescriptions[nIndex] = strDesc;
911
912 // if new icon and no existing icon
dfea7acc 913 if ( m_aIcons[nIndex].empty() )
2b813b73
VZ
914 m_aIcons[nIndex] = strIcon;
915
2b813b73 916 // add any new entries...
678ebfcd 917 if ( entry )
2b813b73 918 {
7c2e5dec 919 wxMimeTypeCommands *entryOld = m_aEntries[nIndex];
678ebfcd
VZ
920
921 size_t count = entry->GetCount();
922 for ( size_t i = 0; i < count; i++ )
923 {
924 const wxString& verb = entry->GetVerb(i);
925 if ( !entryOld->HasVerb(verb) )
926 {
927 entryOld->AddOrReplaceVerb(verb, entry->GetCmd(i));
928 }
929 }
2b293fc7
VZ
930
931 // as we don't store it anywhere, it won't be deleted later as
932 // usual -- do it immediately instead
933 delete entry;
2b813b73
VZ
934 }
935 }
b13d92d1 936 }
4d2976ad 937
678ebfcd
VZ
938 // always add the extensions to this mimetype
939 wxString& exts = m_aExtensions[nIndex];
940
941 // add all extensions we don't have yet
15d4b8cd 942 wxString ext;
678ebfcd
VZ
943 size_t count = strExtensions.GetCount();
944 for ( size_t i = 0; i < count; i++ )
945 {
15d4b8cd
RR
946 ext = strExtensions[i];
947 ext += wxT(' ');
678ebfcd
VZ
948
949 if ( exts.Find(ext) == wxNOT_FOUND )
950 {
951 exts += ext;
952 }
953 }
954
2b813b73 955 // check data integrity
b4a980f4
VZ
956 wxASSERT( m_aTypes.GetCount() == m_aEntries.GetCount() &&
957 m_aTypes.GetCount() == m_aExtensions.GetCount() &&
958 m_aTypes.GetCount() == m_aIcons.GetCount() &&
959 m_aTypes.GetCount() == m_aDescriptions.GetCount() );
cf471cab 960
2b813b73 961 return nIndex;
cf471cab
VS
962}
963
dfea7acc 964wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
b13d92d1 965{
678ebfcd 966 if (ext.empty() )
2b813b73
VZ
967 return NULL;
968
a6c65e88
VZ
969 InitIfNeeded();
970
1ee17e1c 971 size_t count = m_aExtensions.GetCount();
2b813b73 972 for ( size_t n = 0; n < count; n++ )
678ebfcd 973 {
dfea7acc 974 wxStringTokenizer tk(m_aExtensions[n], wxT(' '));
1ee17e1c 975
678ebfcd
VZ
976 while ( tk.HasMoreTokens() )
977 {
1ee17e1c 978 // consider extensions as not being case-sensitive
d0ee33f5 979 if ( tk.GetNextToken().IsSameAs(ext, false /* no case */) )
678ebfcd 980 {
1ee17e1c 981 // found
678ebfcd 982 wxFileType *fileType = new wxFileType;
1ee17e1c 983 fileType->m_impl->Init(this, n);
678ebfcd
VZ
984
985 return fileType;
1ee17e1c
VZ
986 }
987 }
988 }
b13d92d1 989
678ebfcd 990 return NULL;
b13d92d1
VZ
991}
992
7c2e5dec 993wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
b13d92d1 994{
a6c65e88
VZ
995 InitIfNeeded();
996
2b813b73 997 wxFileType * fileType = NULL;
b13d92d1
VZ
998 // mime types are not case-sensitive
999 wxString mimetype(mimeType);
1000 mimetype.MakeLower();
1001
1002 // first look for an exact match
1003 int index = m_aTypes.Index(mimetype);
2b813b73
VZ
1004 if ( index != wxNOT_FOUND )
1005 {
1006 fileType = new wxFileType;
1007 fileType->m_impl->Init(this, index);
1008 }
1009
1010 // then try to find "text/*" as match for "text/plain" (for example)
1011 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1012 // the whole string - ok.
1013
1014 index = wxNOT_FOUND;
1015 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
1016
b4a980f4 1017 size_t nCount = m_aTypes.GetCount();
dfea7acc
DS
1018 for ( size_t n = 0; n < nCount; n++ )
1019 {
2b813b73 1020 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
dfea7acc
DS
1021 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") )
1022 {
2b813b73
VZ
1023 index = n;
1024 break;
b13d92d1
VZ
1025 }
1026 }
1027
2b813b73 1028 if ( index != wxNOT_FOUND )
dfea7acc
DS
1029 {
1030 // don't throw away fileType that was already found
1031 if (!fileType)
7bc5ddc6 1032 fileType = new wxFileType;
b13d92d1 1033 fileType->m_impl->Init(this, index);
b13d92d1 1034 }
dfea7acc 1035
2b813b73
VZ
1036 return fileType;
1037}
1038
2b813b73
VZ
1039wxString wxMimeTypesManagerImpl::GetCommand(const wxString & verb, size_t nIndex) const
1040{
1041 wxString command, testcmd, sV, sTmp;
1042 sV = verb + wxT("=");
dfea7acc 1043
2b813b73 1044 // list of verb = command pairs for this mimetype
678ebfcd 1045 wxMimeTypeCommands * sPairs = m_aEntries [nIndex];
2b813b73
VZ
1046
1047 size_t i;
15d4b8cd
RR
1048 size_t nCount = sPairs->GetCount();
1049 for ( i = 0; i < nCount; i++ )
2b813b73 1050 {
678ebfcd
VZ
1051 sTmp = sPairs->GetVerbCmd (i);
1052 if ( sTmp.Contains(sV) )
1053 command = sTmp.AfterFirst(wxT('='));
b13d92d1 1054 }
dfea7acc 1055
2b813b73 1056 return command;
b13d92d1
VZ
1057}
1058
8e124873
VZ
1059void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1060{
a6c65e88
VZ
1061 InitIfNeeded();
1062
3f1aaa16 1063 wxString extensions;
8e124873
VZ
1064 const wxArrayString& exts = filetype.GetExtensions();
1065 size_t nExts = exts.GetCount();
dfea7acc
DS
1066 for ( size_t nExt = 0; nExt < nExts; nExt++ )
1067 {
1068 if ( nExt > 0 )
223d09f6 1069 extensions += wxT(' ');
dfea7acc 1070
8e124873
VZ
1071 extensions += exts[nExt];
1072 }
1073
1074 AddMimeTypeInfo(filetype.GetMimeType(),
1075 extensions,
1076 filetype.GetDescription());
8e124873
VZ
1077}
1078
1079void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
1080 const wxString& strExtensions,
1081 const wxString& strDesc)
1082{
2b813b73
VZ
1083 // reading mailcap may find image/* , while
1084 // reading mime.types finds image/gif and no match is made
1085 // this means all the get functions don't work fix this
1086 wxString strIcon;
1087 wxString sTmp = strExtensions;
a6c65e88 1088
2b813b73 1089 wxArrayString sExts;
d0ee33f5 1090 sTmp.Trim().Trim(false);
2b813b73 1091
678ebfcd 1092 while (!sTmp.empty())
2b813b73 1093 {
dfea7acc 1094 sExts.Add(sTmp.AfterLast(wxT(' ')));
2b813b73 1095 sTmp = sTmp.BeforeLast(wxT(' '));
8e124873 1096 }
2b813b73 1097
dfea7acc 1098 AddToMimeData(strMimeType, strIcon, NULL, sExts, strDesc, true);
8e124873
VZ
1099}
1100
696e1ea0 1101size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1b986aef 1102{
a6c65e88
VZ
1103 InitIfNeeded();
1104
54acce90
VZ
1105 mimetypes.Empty();
1106
54acce90
VZ
1107 size_t count = m_aTypes.GetCount();
1108 for ( size_t n = 0; n < count; n++ )
1109 {
1110 // don't return template types from here (i.e. anything containg '*')
15d4b8cd 1111 const wxString &type = m_aTypes[n];
dfea7acc 1112 if ( type.Find(wxT('*')) == wxNOT_FOUND )
54acce90
VZ
1113 {
1114 mimetypes.Add(type);
1115 }
1116 }
1b986aef 1117
54acce90 1118 return mimetypes.GetCount();
1b986aef
VZ
1119}
1120
a6c65e88
VZ
1121// ----------------------------------------------------------------------------
1122// writing to MIME type files
1123// ----------------------------------------------------------------------------
1124
2b813b73 1125bool wxMimeTypesManagerImpl::Unassociate(wxFileType *ft)
a6c65e88 1126{
9413e1e3
VZ
1127 InitIfNeeded();
1128
2b813b73 1129 wxArrayString sMimeTypes;
dfea7acc 1130 ft->GetMimeTypes(sMimeTypes);
a6c65e88 1131
2b813b73 1132 size_t i;
15d4b8cd
RR
1133 size_t nCount = sMimeTypes.GetCount();
1134 for (i = 0; i < nCount; i ++)
2b813b73 1135 {
15d4b8cd 1136 const wxString &sMime = sMimeTypes.Item(i);
dfea7acc 1137 int nIndex = m_aTypes.Index(sMime);
2b813b73
VZ
1138 if ( nIndex == wxNOT_FOUND)
1139 {
1140 // error if we get here ??
d0ee33f5 1141 return false;
2b813b73
VZ
1142 }
1143 else
1144 {
dfea7acc 1145 WriteMimeInfo(nIndex, true);
e9d9f136
VZ
1146 m_aTypes.RemoveAt(nIndex);
1147 m_aEntries.RemoveAt(nIndex);
1148 m_aExtensions.RemoveAt(nIndex);
1149 m_aDescriptions.RemoveAt(nIndex);
1150 m_aIcons.RemoveAt(nIndex);
2b813b73
VZ
1151 }
1152 }
1153 // check data integrity
b4a980f4
VZ
1154 wxASSERT( m_aTypes.GetCount() == m_aEntries.GetCount() &&
1155 m_aTypes.GetCount() == m_aExtensions.GetCount() &&
1156 m_aTypes.GetCount() == m_aIcons.GetCount() &&
1157 m_aTypes.GetCount() == m_aDescriptions.GetCount() );
2b813b73 1158
d0ee33f5 1159 return true;
a6c65e88
VZ
1160}
1161
8e124873 1162#endif
b79e32cc 1163 // wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE