1 /////////////////////////////////////////////////////////////////////////////
2 // Name: common/mimetype.cpp
3 // Purpose: classes and functions to manage MIME types
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
13 #pragma implementation "mimetype.h"
16 // for compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
27 #if (wxUSE_FILE && wxUSE_TEXTFILE) || defined(__WXMSW__)
30 #include "wx/string.h"
34 // Doesn't compile in WIN16 mode
40 #include "wx/dynarray.h"
41 #include "wx/confbase.h"
44 #include "wx/msw/registry.h"
47 #include "wx/textfile.h"
50 #include "wx/mimetype.h"
52 // other standard headers
55 // ----------------------------------------------------------------------------
57 // ----------------------------------------------------------------------------
59 // implementation classes, platform dependent
62 // These classes use Windows registry to retrieve the required information.
64 // Keys used (not all of them are documented, so it might actually stop working
65 // in futur versions of Windows...):
66 // 1. "HKCR\MIME\Database\Content Type" contains subkeys for all known MIME
67 // types, each key has a string value "Extension" which gives (dot preceded)
68 // extension for the files of this MIME type.
70 // 2. "HKCR\.ext" contains
71 // a) unnamed value containing the "filetype"
72 // b) value "Content Type" containing the MIME type
74 // 3. "HKCR\filetype" contains
75 // a) unnamed value containing the description
76 // b) subkey "DefaultIcon" with single unnamed value giving the icon index in
78 // c) shell\open\command and shell\open\print subkeys containing the commands
79 // to open/print the file (the positional parameters are introduced by %1,
80 // %2, ... in these strings, we change them to %s ourselves)
82 // although I don't know of any official documentation which mentions this
83 // location, uses it, so it isn't likely to change
84 static const wxChar
*MIME_DATABASE_KEY
= wxT("MIME\\Database\\Content Type\\");
90 wxFileTypeImpl() { m_info
= NULL
; }
92 // one of these Init() function must be called (ctor can't take any
93 // arguments because it's common)
95 // initialize us with our file type name and extension - in this case
96 // we will read all other data from the registry
97 void Init(const wxString
& strFileType
, const wxString
& ext
)
98 { m_strFileType
= strFileType
; m_ext
= ext
; }
100 // initialize us with a wxFileTypeInfo object - it contains all the
102 void Init(const wxFileTypeInfo
& info
)
105 // implement accessor functions
106 bool GetExtensions(wxArrayString
& extensions
);
107 bool GetMimeType(wxString
*mimeType
) const;
108 bool GetIcon(wxIcon
*icon
) const;
109 bool GetDescription(wxString
*desc
) const;
110 bool GetOpenCommand(wxString
*openCmd
,
111 const wxFileType::MessageParameters
& params
) const;
112 bool GetPrintCommand(wxString
*printCmd
,
113 const wxFileType::MessageParameters
& params
) const;
116 // helper function: reads the command corresponding to the specified verb
117 // from the registry (returns an empty string if not found)
118 wxString
GetCommand(const wxChar
*verb
) const;
120 // we use either m_info or read the data from the registry if m_info == NULL
121 const wxFileTypeInfo
*m_info
;
122 wxString m_strFileType
, // may be empty
126 WX_DECLARE_EXPORTED_OBJARRAY(wxFileTypeInfo
, wxArrayFileTypeInfo
);
127 #include "wx/arrimpl.cpp"
128 WX_DEFINE_OBJARRAY(wxArrayFileTypeInfo
);
130 class wxMimeTypesManagerImpl
133 // nothing to do here, we don't load any data but just go and fetch it from
134 // the registry when asked for
135 wxMimeTypesManagerImpl() { }
137 // implement containing class functions
138 wxFileType
*GetFileTypeFromExtension(const wxString
& ext
);
139 wxFileType
*GetFileTypeFromMimeType(const wxString
& mimeType
);
141 size_t EnumAllFileTypes(wxArrayString
& mimetypes
);
143 // this are NOPs under Windows
144 bool ReadMailcap(const wxString
& filename
, bool fallback
= TRUE
)
146 bool ReadMimeTypes(const wxString
& filename
)
149 void AddFallback(const wxFileTypeInfo
& ft
) { m_fallbacks
.Add(ft
); }
152 wxArrayFileTypeInfo m_fallbacks
;
155 #elif defined( __WXMAC__ )
157 WX_DECLARE_EXPORTED_OBJARRAY(wxFileTypeInfo
, wxArrayFileTypeInfo
);
158 #include "wx/arrimpl.cpp"
159 WX_DEFINE_OBJARRAY(wxArrayFileTypeInfo
);
161 class wxMimeTypesManagerImpl
164 wxMimeTypesManagerImpl() { }
166 // implement containing class functions
167 wxFileType
*GetFileTypeFromExtension(const wxString
& ext
);
168 wxFileType
*GetFileTypeFromMimeType(const wxString
& mimeType
);
170 size_t EnumAllFileTypes(wxArrayString
& mimetypes
);
172 // this are NOPs under MacOS
173 bool ReadMailcap(const wxString
& filename
, bool fallback
= TRUE
) { return TRUE
; }
174 bool ReadMimeTypes(const wxString
& filename
) { return TRUE
; }
176 void AddFallback(const wxFileTypeInfo
& ft
) { m_fallbacks
.Add(ft
); }
179 wxArrayFileTypeInfo m_fallbacks
;
185 // initialize us with our file type name
186 void SetFileType(const wxString
& strFileType
)
187 { m_strFileType
= strFileType
; }
188 void SetExt(const wxString
& ext
)
191 // implement accessor functions
192 bool GetExtensions(wxArrayString
& extensions
);
193 bool GetMimeType(wxString
*mimeType
) const;
194 bool GetIcon(wxIcon
*icon
) const;
195 bool GetDescription(wxString
*desc
) const;
196 bool GetOpenCommand(wxString
*openCmd
,
197 const wxFileType::MessageParameters
&) const
198 { return GetCommand(openCmd
, "open"); }
199 bool GetPrintCommand(wxString
*printCmd
,
200 const wxFileType::MessageParameters
&) const
201 { return GetCommand(printCmd
, "print"); }
205 bool GetCommand(wxString
*command
, const char *verb
) const;
207 wxString m_strFileType
, m_ext
;
212 // this class uses both mailcap and mime.types to gather information about file
215 // The information about mailcap file was extracted from metamail(1) sources and
218 // Format of mailcap file: spaces are ignored, each line is either a comment
219 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
220 // A backslash can be used to quote semicolons and newlines (and, in fact,
221 // anything else including itself).
223 // The first field is always the MIME type in the form of type/subtype (see RFC
224 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
225 // "type" which means the same as "type/*", although I'm not sure whether this
228 // The second field is always the command to run. It is subject to
229 // parameter/filename expansion described below.
231 // All the following fields are optional and may not be present at all. If
232 // they're present they may appear in any order, although each of them should
233 // appear only once. The optional fields are the following:
234 // * notes=xxx is an uninterpreted string which is silently ignored
235 // * test=xxx is the command to be used to determine whether this mailcap line
236 // applies to our data or not. The RHS of this field goes through the
237 // parameter/filename expansion (as the 2nd field) and the resulting string
238 // is executed. The line applies only if the command succeeds, i.e. returns 0
240 // * print=xxx is the command to be used to print (and not view) the data of
241 // this type (parameter/filename expansion is done here too)
242 // * edit=xxx is the command to open/edit the data of this type
243 // * needsterminal means that a new console must be created for the viewer
244 // * copiousoutput means that the viewer doesn't interact with the user but
245 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
246 // good example), thus it might be a good idea to use some kind of paging
248 // * textualnewlines means not to perform CR/LF translation (not honored)
249 // * compose and composetyped fields are used to determine the program to be
250 // called to create a new message pert in the specified format (unused).
252 // Parameter/filename xpansion:
253 // * %s is replaced with the (full) file name
254 // * %t is replaced with MIME type/subtype of the entry
255 // * for multipart type only %n is replaced with the nnumber of parts and %F is
256 // replaced by an array of (content-type, temporary file name) pairs for all
257 // message parts (TODO)
258 // * %{parameter} is replaced with the value of parameter taken from
259 // Content-type header line of the message.
261 // FIXME any docs with real descriptions of these files??
263 // There are 2 possible formats for mime.types file, one entry per line (used
264 // for global mime.types) and "expanded" format where an entry takes multiple
265 // lines (used for users mime.types).
267 // For both formats spaces are ignored and lines starting with a '#' are
268 // comments. Each record has one of two following forms:
269 // a) for "brief" format:
270 // <mime type> <space separated list of extensions>
271 // b) for "expanded" format:
272 // type=<mime type> \ desc="<description>" \ exts="ext"
274 // We try to autodetect the format of mime.types: if a non-comment line starts
275 // with "type=" we assume the second format, otherwise the first one.
277 // there may be more than one entry for one and the same mime type, to
278 // choose the right one we have to run the command specified in the test
279 // field on our data.
284 MailCapEntry(const wxString
& openCmd
,
285 const wxString
& printCmd
,
286 const wxString
& testCmd
)
287 : m_openCmd(openCmd
), m_printCmd(printCmd
), m_testCmd(testCmd
)
293 const wxString
& GetOpenCmd() const { return m_openCmd
; }
294 const wxString
& GetPrintCmd() const { return m_printCmd
; }
295 const wxString
& GetTestCmd() const { return m_testCmd
; }
297 MailCapEntry
*GetNext() const { return m_next
; }
300 // prepend this element to the list
301 void Prepend(MailCapEntry
*next
) { m_next
= next
; }
302 // insert into the list at given position
303 void Insert(MailCapEntry
*next
, size_t pos
)
308 for ( cur
= next
; cur
!= NULL
; cur
= cur
->m_next
, n
++ ) {
313 wxASSERT_MSG( n
== pos
, wxT("invalid position in MailCapEntry::Insert") );
315 m_next
= cur
->m_next
;
318 // append this element to the list
319 void Append(MailCapEntry
*next
)
321 wxCHECK_RET( next
!= NULL
, wxT("Append()ing to what?") );
325 for ( cur
= next
; cur
->m_next
!= NULL
; cur
= cur
->m_next
)
330 wxASSERT_MSG( !m_next
, wxT("Append()ing element already in the list?") );
334 wxString m_openCmd
, // command to use to open/view the file
336 m_testCmd
; // only apply this entry if test yields
337 // true (i.e. the command returns 0)
339 MailCapEntry
*m_next
; // in the linked list
342 WX_DEFINE_ARRAY(MailCapEntry
*, ArrayTypeEntries
);
344 class wxMimeTypesManagerImpl
346 friend class wxFileTypeImpl
; // give it access to m_aXXX variables
349 // ctor loads all info into memory for quicker access later on
350 // TODO it would be nice to load them all, but parse on demand only...
351 wxMimeTypesManagerImpl();
353 // implement containing class functions
354 wxFileType
*GetFileTypeFromExtension(const wxString
& ext
);
355 wxFileType
*GetFileTypeFromMimeType(const wxString
& mimeType
);
357 size_t EnumAllFileTypes(wxArrayString
& mimetypes
);
359 bool ReadMailcap(const wxString
& filename
, bool fallback
= FALSE
);
360 bool ReadMimeTypes(const wxString
& filename
);
362 void AddFallback(const wxFileTypeInfo
& filetype
);
364 // add information about the given mimetype
365 void AddMimeTypeInfo(const wxString
& mimetype
,
366 const wxString
& extensions
,
367 const wxString
& description
);
368 void AddMailcapInfo(const wxString
& strType
,
369 const wxString
& strOpenCmd
,
370 const wxString
& strPrintCmd
,
371 const wxString
& strTest
,
372 const wxString
& strDesc
);
375 // get the string containing space separated extensions for the given
377 wxString
GetExtension(size_t index
) { return m_aExtensions
[index
]; }
380 wxArrayString m_aTypes
, // MIME types
381 m_aDescriptions
, // descriptions (just some text)
382 m_aExtensions
; // space separated list of extensions
383 ArrayTypeEntries m_aEntries
; // commands and tests for this file type
389 // initialization functions
390 void Init(wxMimeTypesManagerImpl
*manager
, size_t index
)
391 { m_manager
= manager
; m_index
= index
; }
394 bool GetExtensions(wxArrayString
& extensions
);
395 bool GetMimeType(wxString
*mimeType
) const
396 { *mimeType
= m_manager
->m_aTypes
[m_index
]; return TRUE
; }
397 bool GetIcon(wxIcon
* WXUNUSED(icon
)) const
398 { return FALSE
; } // TODO maybe with Gnome/KDE integration...
399 bool GetDescription(wxString
*desc
) const
400 { *desc
= m_manager
->m_aDescriptions
[m_index
]; return TRUE
; }
402 bool GetOpenCommand(wxString
*openCmd
,
403 const wxFileType::MessageParameters
& params
) const
405 return GetExpandedCommand(openCmd
, params
, TRUE
);
408 bool GetPrintCommand(wxString
*printCmd
,
409 const wxFileType::MessageParameters
& params
) const
411 return GetExpandedCommand(printCmd
, params
, FALSE
);
415 // get the entry which passes the test (may return NULL)
416 MailCapEntry
*GetEntry(const wxFileType::MessageParameters
& params
) const;
418 // choose the correct entry to use and expand the command
419 bool GetExpandedCommand(wxString
*expandedCmd
,
420 const wxFileType::MessageParameters
& params
,
423 wxMimeTypesManagerImpl
*m_manager
;
424 size_t m_index
; // in the wxMimeTypesManagerImpl arrays
429 // ============================================================================
431 // ============================================================================
433 // ----------------------------------------------------------------------------
435 // ----------------------------------------------------------------------------
437 wxFileTypeInfo::wxFileTypeInfo(const char *mimeType
,
439 const char *printCmd
,
442 : m_mimeType(mimeType
),
444 m_printCmd(printCmd
),
448 va_start(argptr
, desc
);
452 const char *ext
= va_arg(argptr
, const char *);
455 // NULL terminates the list
465 // ============================================================================
466 // implementation of the wrapper classes
467 // ============================================================================
469 // ----------------------------------------------------------------------------
471 // ----------------------------------------------------------------------------
473 wxString
wxFileType::ExpandCommand(const wxString
& command
,
474 const wxFileType::MessageParameters
& params
)
476 bool hasFilename
= FALSE
;
479 for ( const wxChar
*pc
= command
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
480 if ( *pc
== wxT('%') ) {
483 // '%s' expands into file name (quoted because it might
484 // contain spaces) - except if there are already quotes
485 // there because otherwise some programs may get confused
486 // by double double quotes
488 if ( *(pc
- 2) == wxT('"') )
489 str
<< params
.GetFileName();
491 str
<< wxT('"') << params
.GetFileName() << wxT('"');
493 str
<< params
.GetFileName();
498 // '%t' expands into MIME type (quote it too just to be
500 str
<< wxT('\'') << params
.GetMimeType() << wxT('\'');
505 const wxChar
*pEnd
= wxStrchr(pc
, wxT('}'));
506 if ( pEnd
== NULL
) {
508 wxLogWarning(_("Unmatched '{' in an entry for "
510 params
.GetMimeType().c_str());
514 wxString
param(pc
+ 1, pEnd
- pc
- 1);
515 str
<< wxT('\'') << params
.GetParamValue(param
) << wxT('\'');
523 // TODO %n is the number of parts, %F is an array containing
524 // the names of temp files these parts were written to
525 // and their mime types.
529 wxLogDebug(wxT("Unknown field %%%c in command '%s'."),
530 *pc
, command
.c_str());
539 // metamail(1) man page states that if the mailcap entry doesn't have '%s'
540 // the program will accept the data on stdin: so give it to it!
541 if ( !hasFilename
&& !str
.IsEmpty() ) {
542 str
<< wxT(" < '") << params
.GetFileName() << wxT('\'');
548 wxFileType::wxFileType()
550 m_impl
= new wxFileTypeImpl
;
553 wxFileType::~wxFileType()
558 bool wxFileType::GetExtensions(wxArrayString
& extensions
)
560 return m_impl
->GetExtensions(extensions
);
563 bool wxFileType::GetMimeType(wxString
*mimeType
) const
565 return m_impl
->GetMimeType(mimeType
);
568 bool wxFileType::GetIcon(wxIcon
*icon
) const
570 return m_impl
->GetIcon(icon
);
573 bool wxFileType::GetDescription(wxString
*desc
) const
575 return m_impl
->GetDescription(desc
);
579 wxFileType::GetOpenCommand(wxString
*openCmd
,
580 const wxFileType::MessageParameters
& params
) const
582 return m_impl
->GetOpenCommand(openCmd
, params
);
586 wxFileType::GetPrintCommand(wxString
*printCmd
,
587 const wxFileType::MessageParameters
& params
) const
589 return m_impl
->GetPrintCommand(printCmd
, params
);
592 // ----------------------------------------------------------------------------
593 // wxMimeTypesManager
594 // ----------------------------------------------------------------------------
596 bool wxMimeTypesManager::IsOfType(const wxString
& mimeType
,
597 const wxString
& wildcard
)
599 wxASSERT_MSG( mimeType
.Find(wxT('*')) == wxNOT_FOUND
,
600 wxT("first MIME type can't contain wildcards") );
602 // all comparaisons are case insensitive (2nd arg of IsSameAs() is FALSE)
603 if ( wildcard
.BeforeFirst(wxT('/')).IsSameAs(mimeType
.BeforeFirst(wxT('/')), FALSE
) )
605 wxString strSubtype
= wildcard
.AfterFirst(wxT('/'));
607 if ( strSubtype
== wxT("*") ||
608 strSubtype
.IsSameAs(mimeType
.AfterFirst(wxT('/')), FALSE
) )
610 // matches (either exactly or it's a wildcard)
618 wxMimeTypesManager::wxMimeTypesManager()
620 m_impl
= new wxMimeTypesManagerImpl
;
623 wxMimeTypesManager::~wxMimeTypesManager()
629 wxMimeTypesManager::GetFileTypeFromExtension(const wxString
& ext
)
631 return m_impl
->GetFileTypeFromExtension(ext
);
635 wxMimeTypesManager::GetFileTypeFromMimeType(const wxString
& mimeType
)
637 return m_impl
->GetFileTypeFromMimeType(mimeType
);
640 bool wxMimeTypesManager::ReadMailcap(const wxString
& filename
, bool fallback
)
642 return m_impl
->ReadMailcap(filename
, fallback
);
645 bool wxMimeTypesManager::ReadMimeTypes(const wxString
& filename
)
647 return m_impl
->ReadMimeTypes(filename
);
650 void wxMimeTypesManager::AddFallbacks(const wxFileTypeInfo
*filetypes
)
652 for ( const wxFileTypeInfo
*ft
= filetypes
; ft
->IsValid(); ft
++ ) {
653 m_impl
->AddFallback(*ft
);
657 size_t wxMimeTypesManager::EnumAllFileTypes(wxArrayString
& mimetypes
)
659 return m_impl
->EnumAllFileTypes(mimetypes
);
662 // ============================================================================
663 // real (OS specific) implementation
664 // ============================================================================
668 wxString
wxFileTypeImpl::GetCommand(const wxChar
*verb
) const
670 // suppress possible error messages
674 if ( wxRegKey(wxRegKey::HKCR
, m_ext
+ _T("\\shell")).Exists() )
676 if ( wxRegKey(wxRegKey::HKCR
, m_strFileType
+ _T("\\shell")).Exists() )
677 strKey
= m_strFileType
;
682 return wxEmptyString
;
685 strKey
<< wxT("\\shell\\") << verb
<< wxT("\\command");
686 wxRegKey
key(wxRegKey::HKCR
, strKey
);
689 // it's the default value of the key
690 if ( key
.QueryValue(wxT(""), command
) ) {
691 // transform it from '%1' to '%s' style format string
693 // NB: we don't make any attempt to verify that the string is valid,
694 // i.e. doesn't contain %2, or second %1 or .... But we do make
695 // sure that we return a string with _exactly_ one '%s'!
696 bool foundFilename
= FALSE
;
697 size_t len
= command
.Len();
698 for ( size_t n
= 0; (n
< len
) && !foundFilename
; n
++ ) {
699 if ( command
[n
] == wxT('%') &&
700 (n
+ 1 < len
) && command
[n
+ 1] == wxT('1') ) {
701 // replace it with '%s'
702 command
[n
+ 1] = wxT('s');
704 foundFilename
= TRUE
;
708 if ( !foundFilename
) {
709 // we didn't find any '%1'!
710 // HACK: append the filename at the end, hope that it will do
711 command
<< wxT(" %s");
715 //else: no such file type or no value, will return empty string
721 wxFileTypeImpl::GetOpenCommand(wxString
*openCmd
,
722 const wxFileType::MessageParameters
& params
)
727 cmd
= m_info
->GetOpenCommand();
730 cmd
= GetCommand(wxT("open"));
733 *openCmd
= wxFileType::ExpandCommand(cmd
, params
);
735 return !openCmd
->IsEmpty();
739 wxFileTypeImpl::GetPrintCommand(wxString
*printCmd
,
740 const wxFileType::MessageParameters
& params
)
745 cmd
= m_info
->GetPrintCommand();
748 cmd
= GetCommand(wxT("print"));
751 *printCmd
= wxFileType::ExpandCommand(cmd
, params
);
753 return !printCmd
->IsEmpty();
756 // TODO this function is half implemented
757 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
760 extensions
= m_info
->GetExtensions();
764 else if ( m_ext
.IsEmpty() ) {
765 // the only way to get the list of extensions from the file type is to
766 // scan through all extensions in the registry - too slow...
771 extensions
.Add(m_ext
);
773 // it's a lie too, we don't return _all_ extensions...
778 bool wxFileTypeImpl::GetMimeType(wxString
*mimeType
) const
781 // we already have it
782 *mimeType
= m_info
->GetMimeType();
787 // suppress possible error messages
789 wxRegKey
key(wxRegKey::HKCR
, wxT(".") + m_ext
);
790 if ( key
.Open() && key
.QueryValue(wxT("Content Type"), *mimeType
) ) {
798 bool wxFileTypeImpl::GetIcon(wxIcon
*icon
) const
802 // we don't have icons in the fallback resources
807 strIconKey
<< m_strFileType
<< wxT("\\DefaultIcon");
809 // suppress possible error messages
811 wxRegKey
key(wxRegKey::HKCR
, strIconKey
);
815 // it's the default value of the key
816 if ( key
.QueryValue(wxT(""), strIcon
) ) {
817 // the format is the following: <full path to file>, <icon index>
818 // NB: icon index may be negative as well as positive and the full
819 // path may contain the environment variables inside '%'
820 wxString strFullPath
= strIcon
.BeforeLast(wxT(',')),
821 strIndex
= strIcon
.AfterLast(wxT(','));
823 // index may be omitted, in which case BeforeLast(',') is empty and
824 // AfterLast(',') is the whole string
825 if ( strFullPath
.IsEmpty() ) {
826 strFullPath
= strIndex
;
830 wxString strExpPath
= wxExpandEnvVars(strFullPath
);
831 int nIndex
= wxAtoi(strIndex
);
833 HICON hIcon
= ExtractIcon(GetModuleHandle(NULL
), strExpPath
, nIndex
);
834 switch ( (int)hIcon
) {
835 case 0: // means no icons were found
836 case 1: // means no such file or it wasn't a DLL/EXE/OCX/ICO/...
837 wxLogDebug(wxT("incorrect registry entry '%s': no such icon."),
838 key
.GetName().c_str());
842 icon
->SetHICON((WXHICON
)hIcon
);
848 // no such file type or no value or incorrect icon entry
854 bool wxFileTypeImpl::GetDescription(wxString
*desc
) const
857 // we already have it
858 *desc
= m_info
->GetDescription();
863 // suppress possible error messages
865 wxRegKey
key(wxRegKey::HKCR
, m_strFileType
);
868 // it's the default value of the key
869 if ( key
.QueryValue(wxT(""), *desc
) ) {
877 // extension -> file type
879 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
881 // add the leading point if necessary
883 if ( ext
[0u] != wxT('.') ) {
888 // suppress possible error messages
891 bool knownExtension
= FALSE
;
893 wxString strFileType
;
894 wxRegKey
key(wxRegKey::HKCR
, str
);
896 // it's the default value of the key
897 if ( key
.QueryValue(wxT(""), strFileType
) ) {
898 // create the new wxFileType object
899 wxFileType
*fileType
= new wxFileType
;
900 fileType
->m_impl
->Init(strFileType
, ext
);
905 // this extension doesn't have a filetype, but it's known to the
906 // system and may be has some other useful keys (open command or
907 // content-type), so still return a file type object for it
908 knownExtension
= TRUE
;
912 // check the fallbacks
913 // TODO linear search is potentially slow, perhaps we should use a sorted
915 size_t count
= m_fallbacks
.GetCount();
916 for ( size_t n
= 0; n
< count
; n
++ ) {
917 if ( m_fallbacks
[n
].GetExtensions().Index(ext
) != wxNOT_FOUND
) {
918 wxFileType
*fileType
= new wxFileType
;
919 fileType
->m_impl
->Init(m_fallbacks
[n
]);
925 if ( knownExtension
)
927 wxFileType
*fileType
= new wxFileType
;
928 fileType
->m_impl
->Init(wxEmptyString
, ext
);
939 // MIME type -> extension -> file type
941 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
943 wxString strKey
= MIME_DATABASE_KEY
;
946 // suppress possible error messages
950 wxRegKey
key(wxRegKey::HKCR
, strKey
);
952 if ( key
.QueryValue(wxT("Extension"), ext
) ) {
953 return GetFileTypeFromExtension(ext
);
957 // check the fallbacks
958 // TODO linear search is potentially slow, perhaps we should use a sorted
960 size_t count
= m_fallbacks
.GetCount();
961 for ( size_t n
= 0; n
< count
; n
++ ) {
962 if ( wxMimeTypesManager::IsOfType(mimeType
,
963 m_fallbacks
[n
].GetMimeType()) ) {
964 wxFileType
*fileType
= new wxFileType
;
965 fileType
->m_impl
->Init(m_fallbacks
[n
]);
975 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& mimetypes
)
977 // enumerate all keys under MIME_DATABASE_KEY
978 wxRegKey
key(wxRegKey::HKCR
, MIME_DATABASE_KEY
);
982 bool cont
= key
.GetFirstKey(type
, cookie
);
987 cont
= key
.GetNextKey(type
, cookie
);
990 return mimetypes
.GetCount();
993 #elif defined ( __WXMAC__ )
995 bool wxFileTypeImpl::GetCommand(wxString
*command
, const char *verb
) const
1000 // @@ this function is half implemented
1001 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
1006 bool wxFileTypeImpl::GetMimeType(wxString
*mimeType
) const
1008 if ( m_strFileType
.Length() > 0 )
1010 *mimeType
= m_strFileType
;
1017 bool wxFileTypeImpl::GetIcon(wxIcon
*icon
) const
1019 // no such file type or no value or incorrect icon entry
1023 bool wxFileTypeImpl::GetDescription(wxString
*desc
) const
1028 // extension -> file type
1030 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& e
)
1036 wxFileType
*fileType
= new wxFileType
;
1037 fileType
->m_impl
->SetFileType("text/text");
1038 fileType
->m_impl
->SetExt(ext
);
1041 else if ( ext
== "htm" || ext
== "html" )
1043 wxFileType
*fileType
= new wxFileType
;
1044 fileType
->m_impl
->SetFileType("text/html");
1045 fileType
->m_impl
->SetExt(ext
);
1048 else if ( ext
== "gif" )
1050 wxFileType
*fileType
= new wxFileType
;
1051 fileType
->m_impl
->SetFileType("image/gif");
1052 fileType
->m_impl
->SetExt(ext
);
1055 else if ( ext
== "png" )
1057 wxFileType
*fileType
= new wxFileType
;
1058 fileType
->m_impl
->SetFileType("image/png");
1059 fileType
->m_impl
->SetExt(ext
);
1062 else if ( ext
== "jpg" || ext
== "jpeg" )
1064 wxFileType
*fileType
= new wxFileType
;
1065 fileType
->m_impl
->SetFileType("image/jpeg");
1066 fileType
->m_impl
->SetExt(ext
);
1069 else if ( ext
== "bmp" )
1071 wxFileType
*fileType
= new wxFileType
;
1072 fileType
->m_impl
->SetFileType("image/bmp");
1073 fileType
->m_impl
->SetExt(ext
);
1076 else if ( ext
== "tif" || ext
== "tiff" )
1078 wxFileType
*fileType
= new wxFileType
;
1079 fileType
->m_impl
->SetFileType("image/tiff");
1080 fileType
->m_impl
->SetExt(ext
);
1083 else if ( ext
== "xpm" )
1085 wxFileType
*fileType
= new wxFileType
;
1086 fileType
->m_impl
->SetFileType("image/xpm");
1087 fileType
->m_impl
->SetExt(ext
);
1090 else if ( ext
== "xbm" )
1092 wxFileType
*fileType
= new wxFileType
;
1093 fileType
->m_impl
->SetFileType("image/xbm");
1094 fileType
->m_impl
->SetExt(ext
);
1098 // unknown extension
1102 // MIME type -> extension -> file type
1104 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
1109 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& mimetypes
)
1111 wxFAIL_MSG( _T("TODO") ); // VZ: don't know anything about this for Mac
1119 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters
& params
) const
1122 MailCapEntry
*entry
= m_manager
->m_aEntries
[m_index
];
1123 while ( entry
!= NULL
) {
1124 // notice that an empty command would always succeed (it's ok)
1125 command
= wxFileType::ExpandCommand(entry
->GetTestCmd(), params
);
1127 if ( command
.IsEmpty() || (wxSystem(command
) == 0) ) {
1129 wxLogTrace(wxT("Test '%s' for mime type '%s' succeeded."),
1130 command
.c_str(), params
.GetMimeType().c_str());
1134 wxLogTrace(wxT("Test '%s' for mime type '%s' failed."),
1135 command
.c_str(), params
.GetMimeType().c_str());
1138 entry
= entry
->GetNext();
1145 wxFileTypeImpl::GetExpandedCommand(wxString
*expandedCmd
,
1146 const wxFileType::MessageParameters
& params
,
1149 MailCapEntry
*entry
= GetEntry(params
);
1150 if ( entry
== NULL
) {
1151 // all tests failed...
1155 wxString cmd
= open
? entry
->GetOpenCmd() : entry
->GetPrintCmd();
1156 if ( cmd
.IsEmpty() ) {
1157 // may happen, especially for "print"
1161 *expandedCmd
= wxFileType::ExpandCommand(cmd
, params
);
1165 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
1167 wxString strExtensions
= m_manager
->GetExtension(m_index
);
1170 // one extension in the space or comma delimitid list
1172 for ( const wxChar
*p
= strExtensions
; ; p
++ ) {
1173 if ( *p
== wxT(' ') || *p
== wxT(',') || *p
== wxT('\0') ) {
1174 if ( !strExt
.IsEmpty() ) {
1175 extensions
.Add(strExt
);
1178 //else: repeated spaces (shouldn't happen, but it's not that
1179 // important if it does happen)
1181 if ( *p
== wxT('\0') )
1184 else if ( *p
== wxT('.') ) {
1185 // remove the dot from extension (but only if it's the first char)
1186 if ( !strExt
.IsEmpty() ) {
1189 //else: no, don't append it
1199 // read system and user mailcaps (TODO implement mime.types support)
1200 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1202 // directories where we look for mailcap and mime.types by default
1203 // (taken from metamail(1) sources)
1204 static const wxChar
*aStandardLocations
[] =
1208 wxT("/usr/local/etc"),
1210 wxT("/usr/public/lib")
1213 // first read the system wide file(s)
1214 for ( size_t n
= 0; n
< WXSIZEOF(aStandardLocations
); n
++ ) {
1215 wxString dir
= aStandardLocations
[n
];
1217 wxString file
= dir
+ wxT("/mailcap");
1218 if ( wxFile::Exists(file
) ) {
1222 file
= dir
+ wxT("/mime.types");
1223 if ( wxFile::Exists(file
) ) {
1224 ReadMimeTypes(file
);
1228 wxString strHome
= wxGetenv(wxT("HOME"));
1230 // and now the users mailcap
1231 wxString strUserMailcap
= strHome
+ wxT("/.mailcap");
1232 if ( wxFile::Exists(strUserMailcap
) ) {
1233 ReadMailcap(strUserMailcap
);
1236 // read the users mime.types
1237 wxString strUserMimeTypes
= strHome
+ wxT("/.mime.types");
1238 if ( wxFile::Exists(strUserMimeTypes
) ) {
1239 ReadMimeTypes(strUserMimeTypes
);
1244 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
1246 size_t count
= m_aExtensions
.GetCount();
1247 for ( size_t n
= 0; n
< count
; n
++ ) {
1248 wxString extensions
= m_aExtensions
[n
];
1249 while ( !extensions
.IsEmpty() ) {
1250 wxString field
= extensions
.BeforeFirst(wxT(' '));
1251 extensions
= extensions
.AfterFirst(wxT(' '));
1253 // consider extensions as not being case-sensitive
1254 if ( field
.IsSameAs(ext
, FALSE
/* no case */) ) {
1256 wxFileType
*fileType
= new wxFileType
;
1257 fileType
->m_impl
->Init(this, n
);
1269 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
1271 // mime types are not case-sensitive
1272 wxString
mimetype(mimeType
);
1273 mimetype
.MakeLower();
1275 // first look for an exact match
1276 int index
= m_aTypes
.Index(mimetype
);
1277 if ( index
== wxNOT_FOUND
) {
1278 // then try to find "text/*" as match for "text/plain" (for example)
1279 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1280 // the whole string - ok.
1281 wxString strCategory
= mimetype
.BeforeFirst(wxT('/'));
1283 size_t nCount
= m_aTypes
.Count();
1284 for ( size_t n
= 0; n
< nCount
; n
++ ) {
1285 if ( (m_aTypes
[n
].BeforeFirst(wxT('/')) == strCategory
) &&
1286 m_aTypes
[n
].AfterFirst(wxT('/')) == wxT("*") ) {
1293 if ( index
!= wxNOT_FOUND
) {
1294 wxFileType
*fileType
= new wxFileType
;
1295 fileType
->m_impl
->Init(this, index
);
1305 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo
& filetype
)
1307 wxString extensions
;
1308 const wxArrayString
& exts
= filetype
.GetExtensions();
1309 size_t nExts
= exts
.GetCount();
1310 for ( size_t nExt
= 0; nExt
< nExts
; nExt
++ ) {
1312 extensions
+= wxT(' ');
1314 extensions
+= exts
[nExt
];
1317 AddMimeTypeInfo(filetype
.GetMimeType(),
1319 filetype
.GetDescription());
1321 AddMailcapInfo(filetype
.GetMimeType(),
1322 filetype
.GetOpenCommand(),
1323 filetype
.GetPrintCommand(),
1325 filetype
.GetDescription());
1328 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString
& strMimeType
,
1329 const wxString
& strExtensions
,
1330 const wxString
& strDesc
)
1332 int index
= m_aTypes
.Index(strMimeType
);
1333 if ( index
== wxNOT_FOUND
) {
1335 m_aTypes
.Add(strMimeType
);
1336 m_aEntries
.Add(NULL
);
1337 m_aExtensions
.Add(strExtensions
);
1338 m_aDescriptions
.Add(strDesc
);
1341 // modify an existing one
1342 if ( !strDesc
.IsEmpty() ) {
1343 m_aDescriptions
[index
] = strDesc
; // replace old value
1345 m_aExtensions
[index
] += ' ' + strExtensions
;
1349 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString
& strType
,
1350 const wxString
& strOpenCmd
,
1351 const wxString
& strPrintCmd
,
1352 const wxString
& strTest
,
1353 const wxString
& strDesc
)
1355 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
, strPrintCmd
, strTest
);
1357 int nIndex
= m_aTypes
.Index(strType
);
1358 if ( nIndex
== wxNOT_FOUND
) {
1360 m_aTypes
.Add(strType
);
1362 m_aEntries
.Add(entry
);
1363 m_aExtensions
.Add(wxT(""));
1364 m_aDescriptions
.Add(strDesc
);
1367 // always append the entry in the tail of the list - info added with
1368 // this function can only come from AddFallbacks()
1369 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1371 entry
->Append(entryOld
);
1373 m_aEntries
[nIndex
] = entry
;
1377 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString
& strFileName
)
1379 wxLogTrace(wxT("--- Parsing mime.types file '%s' ---"), strFileName
.c_str());
1381 wxTextFile
file(strFileName
);
1385 // the information we extract
1386 wxString strMimeType
, strDesc
, strExtensions
;
1388 size_t nLineCount
= file
.GetLineCount();
1389 const wxChar
*pc
= NULL
;
1390 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1392 // now we're at the start of the line
1393 pc
= file
[nLine
].c_str();
1396 // we didn't finish with the previous line yet
1401 while ( wxIsspace(*pc
) )
1405 if ( *pc
== wxT('#') ) {
1406 // skip the whole line
1411 // detect file format
1412 const wxChar
*pEqualSign
= wxStrchr(pc
, wxT('='));
1413 if ( pEqualSign
== NULL
) {
1417 // first field is mime type
1418 for ( strMimeType
.Empty(); !wxIsspace(*pc
) && *pc
!= wxT('\0'); pc
++ ) {
1423 while ( wxIsspace(*pc
) )
1426 // take all the rest of the string
1429 // no description...
1436 // the string on the left of '=' is the field name
1437 wxString
strLHS(pc
, pEqualSign
- pc
);
1440 for ( pc
= pEqualSign
+ 1; wxIsspace(*pc
); pc
++ )
1444 if ( *pc
== wxT('"') ) {
1445 // the string is quoted and ends at the matching quote
1446 pEnd
= wxStrchr(++pc
, wxT('"'));
1447 if ( pEnd
== NULL
) {
1448 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1450 strFileName
.c_str(), nLine
+ 1);
1454 // unquoted string ends at the first space
1455 for ( pEnd
= pc
; !wxIsspace(*pEnd
); pEnd
++ )
1459 // now we have the RHS (field value)
1460 wxString
strRHS(pc
, pEnd
- pc
);
1462 // check what follows this entry
1463 if ( *pEnd
== wxT('"') ) {
1468 for ( pc
= pEnd
; wxIsspace(*pc
); pc
++ )
1471 // if there is something left, it may be either a '\\' to continue
1472 // the line or the next field of the same entry
1473 bool entryEnded
= *pc
== wxT('\0'),
1474 nextFieldOnSameLine
= FALSE
;
1475 if ( !entryEnded
) {
1476 nextFieldOnSameLine
= ((*pc
!= wxT('\\')) || (pc
[1] != wxT('\0')));
1479 // now see what we got
1480 if ( strLHS
== wxT("type") ) {
1481 strMimeType
= strRHS
;
1483 else if ( strLHS
== wxT("desc") ) {
1486 else if ( strLHS
== wxT("exts") ) {
1487 strExtensions
= strRHS
;
1490 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1491 strFileName
.c_str(), nLine
+ 1, strLHS
.c_str());
1494 if ( !entryEnded
) {
1495 if ( !nextFieldOnSameLine
)
1497 //else: don't reset it
1499 // as we don't reset strMimeType, the next field in this entry
1500 // will be interpreted correctly.
1506 // although it doesn't seem to be covered by RFCs, some programs
1507 // (notably Netscape) create their entries with several comma
1508 // separated extensions (RFC mention the spaces only)
1509 strExtensions
.Replace(wxT(","), wxT(" "));
1511 // also deal with the leading dot
1512 if ( !strExtensions
.IsEmpty() && strExtensions
[0u] == wxT('.') )
1514 strExtensions
.erase(0, 1);
1517 AddMimeTypeInfo(strMimeType
, strExtensions
, strDesc
);
1519 // finished with this line
1523 // check our data integriry
1524 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1525 m_aTypes
.Count() == m_aExtensions
.Count() &&
1526 m_aTypes
.Count() == m_aDescriptions
.Count() );
1531 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString
& strFileName
,
1534 wxLogTrace(wxT("--- Parsing mailcap file '%s' ---"), strFileName
.c_str());
1536 wxTextFile
file(strFileName
);
1540 // see the comments near the end of function for the reason we need these
1541 // variables (search for the next occurence of them)
1542 // indices of MIME types (in m_aTypes) we already found in this file
1543 wxArrayInt aEntryIndices
;
1544 // aLastIndices[n] is the index of last element in
1545 // m_aEntries[aEntryIndices[n]] from this file
1546 wxArrayInt aLastIndices
;
1548 size_t nLineCount
= file
.GetLineCount();
1549 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1550 // now we're at the start of the line
1551 const wxChar
*pc
= file
[nLine
].c_str();
1554 while ( wxIsspace(*pc
) )
1557 // comment or empty string?
1558 if ( *pc
== wxT('#') || *pc
== wxT('\0') )
1563 // what field are we currently in? The first 2 are fixed and there may
1564 // be an arbitrary number of other fields -- currently, we are not
1565 // interested in any of them, but we should parse them as well...
1571 } currentToken
= Field_Type
;
1573 // the flags and field values on the current line
1574 bool needsterminal
= FALSE
,
1575 copiousoutput
= FALSE
;
1581 curField
; // accumulator
1582 for ( bool cont
= TRUE
; cont
; pc
++ ) {
1585 // interpret the next character literally (notice that
1586 // backslash can be used for line continuation)
1587 if ( *++pc
== wxT('\0') ) {
1588 // fetch the next line.
1590 // pc currently points to nowhere, but after the next
1591 // pc++ in the for line it will point to the beginning
1592 // of the next line in the file
1593 pc
= file
[++nLine
].c_str() - 1;
1596 // just a normal character
1602 cont
= FALSE
; // end of line reached, exit the loop
1607 // store this field and start looking for the next one
1609 // trim whitespaces from both sides
1610 curField
.Trim(TRUE
).Trim(FALSE
);
1612 switch ( currentToken
) {
1615 if ( strType
.Find(wxT('/')) == wxNOT_FOUND
) {
1616 // we interpret "type" as "type/*"
1617 strType
+= wxT("/*");
1620 currentToken
= Field_OpenCmd
;
1624 strOpenCmd
= curField
;
1626 currentToken
= Field_Other
;
1631 // "good" mailcap entry?
1634 // is this something of the form foo=bar?
1635 const wxChar
*pEq
= wxStrchr(curField
, wxT('='));
1636 if ( pEq
!= NULL
) {
1637 wxString lhs
= curField
.BeforeFirst(wxT('=')),
1638 rhs
= curField
.AfterFirst(wxT('='));
1640 lhs
.Trim(TRUE
); // from right
1641 rhs
.Trim(FALSE
); // from left
1643 if ( lhs
== wxT("print") )
1645 else if ( lhs
== wxT("test") )
1647 else if ( lhs
== wxT("description") ) {
1648 // it might be quoted
1649 if ( rhs
[0u] == wxT('"') &&
1650 rhs
.Last() == wxT('"') ) {
1651 strDesc
= wxString(rhs
.c_str() + 1,
1658 else if ( lhs
== wxT("compose") ||
1659 lhs
== wxT("composetyped") ||
1660 lhs
== wxT("notes") ||
1661 lhs
== wxT("edit") )
1668 // no, it's a simple flag
1669 // TODO support the flags:
1670 // 1. create an xterm for 'needsterminal'
1671 // 2. append "| $PAGER" for 'copiousoutput'
1672 if ( curField
== wxT("needsterminal") )
1673 needsterminal
= TRUE
;
1674 else if ( curField
== wxT("copiousoutput") )
1675 copiousoutput
= TRUE
;
1676 else if ( curField
== wxT("textualnewlines") )
1684 // don't flood the user with error messages
1685 // if we don't understand something in his
1686 // mailcap, but give them in debug mode
1687 // because this might be useful for the
1691 wxT("Mailcap file %s, line %d: unknown "
1692 "field '%s' for the MIME type "
1694 strFileName
.c_str(),
1702 // it already has this value
1703 //currentToken = Field_Other;
1707 wxFAIL_MSG(wxT("unknown field type in mailcap"));
1710 // next token starts immediately after ';'
1719 // check that we really read something reasonable
1720 if ( currentToken
== Field_Type
|| currentToken
== Field_OpenCmd
) {
1721 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1723 strFileName
.c_str(), nLine
+ 1);
1726 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
,
1730 // NB: because of complications below (we must get entries priority
1731 // right), we can't use AddMailcapInfo() here, unfortunately.
1732 strType
.MakeLower();
1733 int nIndex
= m_aTypes
.Index(strType
);
1734 if ( nIndex
== wxNOT_FOUND
) {
1736 m_aTypes
.Add(strType
);
1738 m_aEntries
.Add(entry
);
1739 m_aExtensions
.Add(wxT(""));
1740 m_aDescriptions
.Add(strDesc
);
1743 // modify the existing entry: the entries in one and the same
1744 // file are read in top-to-bottom order, i.e. the entries read
1745 // first should be tried before the entries below. However,
1746 // the files read later should override the settings in the
1747 // files read before (except if fallback is TRUE), thus we
1748 // Insert() the new entry to the list if it has already
1749 // occured in _this_ file, but Prepend() it if it occured in
1750 // some of the previous ones and Append() to it in the
1754 // 'fallback' parameter prevents the entries from this
1755 // file from overriding the other ones - always append
1756 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1758 entry
->Append(entryOld
);
1760 m_aEntries
[nIndex
] = entry
;
1763 int entryIndex
= aEntryIndices
.Index(nIndex
);
1764 if ( entryIndex
== wxNOT_FOUND
) {
1765 // first time in this file
1766 aEntryIndices
.Add(nIndex
);
1767 aLastIndices
.Add(0);
1769 entry
->Prepend(m_aEntries
[nIndex
]);
1770 m_aEntries
[nIndex
] = entry
;
1773 // not the first time in _this_ file
1774 size_t nEntryIndex
= (size_t)entryIndex
;
1775 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1777 entry
->Insert(entryOld
, aLastIndices
[nEntryIndex
]);
1779 m_aEntries
[nIndex
] = entry
;
1781 // the indices were shifted by 1
1782 aLastIndices
[nEntryIndex
]++;
1786 if ( !strDesc
.IsEmpty() ) {
1787 // replace the old one - what else can we do??
1788 m_aDescriptions
[nIndex
] = strDesc
;
1793 // check our data integriry
1794 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1795 m_aTypes
.Count() == m_aExtensions
.Count() &&
1796 m_aTypes
.Count() == m_aDescriptions
.Count() );
1802 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& mimetypes
)
1804 mimetypes
= m_aTypes
;
1806 return m_aTypes
.GetCount();
1813 // wxUSE_FILE && wxUSE_TEXTFILE