X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/1ee17e1c421b64b3a356fee82f454ab4b43ab50c..3b59cdbfaf7df79499424c92ff6177736429ff8a:/src/common/mimetype.cpp diff --git a/src/common/mimetype.cpp b/src/common/mimetype.cpp index 513c35b3fc..3dab4f75fa 100644 --- a/src/common/mimetype.cpp +++ b/src/common/mimetype.cpp @@ -10,34 +10,34 @@ ///////////////////////////////////////////////////////////////////////////// #ifdef __GNUG__ - #pragma implementation "mimetype.h" +#pragma implementation "mimetype.h" #endif -// ============================================================================ -// declarations -// ============================================================================ - -// ---------------------------------------------------------------------------- -// headers -// ---------------------------------------------------------------------------- - // for compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ - #pragma hdrstop + #pragma hdrstop +#endif + +#ifndef WX_PRECOMP + #include "wx/defs.h" #endif -// wxWindows +#if (wxUSE_FILE && wxUSE_TEXTFILE) || defined(__WXMSW__) + #ifndef WX_PRECOMP - #include "wx/string.h" - #include "wx/icon.h" + #include "wx/string.h" + #if wxUSE_GUI + #include "wx/icon.h" + #endif #endif //WX_PRECOMP // Doesn't compile in WIN16 mode #ifndef __WIN16__ #include "wx/log.h" +#include "wx/file.h" #include "wx/intl.h" #include "wx/dynarray.h" #include "wx/confbase.h" @@ -45,8 +45,11 @@ #ifdef __WXMSW__ #include "wx/msw/registry.h" #include "windows.h" -#else // Unix +#elif defined(__UNIX__) || defined(__WXPM__) + #include "wx/ffile.h" #include "wx/textfile.h" + #include "wx/dir.h" + #include "wx/utils.h" #endif // OS #include "wx/mimetype.h" @@ -54,6 +57,9 @@ // other standard headers #include +// in case we're compiling in non-GUI mode +class WXDLLEXPORT wxIcon; + // ---------------------------------------------------------------------------- // private classes // ---------------------------------------------------------------------------- @@ -81,17 +87,28 @@ // to open/print the file (the positional parameters are introduced by %1, // %2, ... in these strings, we change them to %s ourselves) +// although I don't know of any official documentation which mentions this +// location, uses it, so it isn't likely to change +static const wxChar *MIME_DATABASE_KEY = wxT("MIME\\Database\\Content Type\\"); + class wxFileTypeImpl { public: // ctor - wxFileTypeImpl() { } + wxFileTypeImpl() { m_info = NULL; } - // initialize us with our file type name - void SetFileType(const wxString& strFileType) - { m_strFileType = strFileType; } - void SetExt(const wxString& ext) - { m_ext = ext; } + // one of these Init() function must be called (ctor can't take any + // arguments because it's common) + + // initialize us with our file type name and extension - in this case + // we will read all other data from the registry + void Init(const wxString& strFileType, const wxString& ext) + { m_strFileType = strFileType; m_ext = ext; } + + // initialize us with a wxFileTypeInfo object - it contains all the + // data + void Init(const wxFileTypeInfo& info) + { m_info = &info; } // implement accessor functions bool GetExtensions(wxArrayString& extensions); @@ -99,19 +116,25 @@ public: bool GetIcon(wxIcon *icon) const; bool GetDescription(wxString *desc) const; bool GetOpenCommand(wxString *openCmd, - const wxFileType::MessageParameters&) const - { return GetCommand(openCmd, "open"); } + const wxFileType::MessageParameters& params) const; bool GetPrintCommand(wxString *printCmd, - const wxFileType::MessageParameters&) const - { return GetCommand(printCmd, "print"); } + const wxFileType::MessageParameters& params) const; private: - // helper function - bool GetCommand(wxString *command, const char *verb) const; - - wxString m_strFileType, m_ext; + // helper function: reads the command corresponding to the specified verb + // from the registry (returns an empty string if not found) + wxString GetCommand(const wxChar *verb) const; + + // we use either m_info or read the data from the registry if m_info == NULL + const wxFileTypeInfo *m_info; + wxString m_strFileType, // may be empty + m_ext; }; +WX_DECLARE_EXPORTED_OBJARRAY(wxFileTypeInfo, wxArrayFileTypeInfo); +#include "wx/arrimpl.cpp" +WX_DEFINE_OBJARRAY(wxArrayFileTypeInfo); + class wxMimeTypesManagerImpl { public: @@ -123,9 +146,73 @@ public: wxFileType *GetFileTypeFromExtension(const wxString& ext); wxFileType *GetFileTypeFromMimeType(const wxString& mimeType); + size_t EnumAllFileTypes(wxArrayString& mimetypes); + // this are NOPs under Windows - void ReadMailcap(const wxString& filename) { } - void ReadMimeTypes(const wxString& filename) { } + bool ReadMailcap(const wxString& filename, bool fallback = TRUE) + { return TRUE; } + bool ReadMimeTypes(const wxString& filename) + { return TRUE; } + + void AddFallback(const wxFileTypeInfo& ft) { m_fallbacks.Add(ft); } + +private: + wxArrayFileTypeInfo m_fallbacks; +}; + +#elif defined( __WXMAC__ ) + +WX_DECLARE_EXPORTED_OBJARRAY(wxFileTypeInfo, wxArrayFileTypeInfo); +#include "wx/arrimpl.cpp" +WX_DEFINE_OBJARRAY(wxArrayFileTypeInfo); + +class wxMimeTypesManagerImpl +{ +public : + wxMimeTypesManagerImpl() { } + + // implement containing class functions + wxFileType *GetFileTypeFromExtension(const wxString& ext); + wxFileType *GetFileTypeFromMimeType(const wxString& mimeType); + + size_t EnumAllFileTypes(wxArrayString& mimetypes); + + // this are NOPs under MacOS + bool ReadMailcap(const wxString& filename, bool fallback = TRUE) { return TRUE; } + bool ReadMimeTypes(const wxString& filename) { return TRUE; } + + void AddFallback(const wxFileTypeInfo& ft) { m_fallbacks.Add(ft); } + +private: + wxArrayFileTypeInfo m_fallbacks; +}; + +class wxFileTypeImpl +{ +public: + // initialize us with our file type name + void SetFileType(const wxString& strFileType) + { m_strFileType = strFileType; } + void SetExt(const wxString& ext) + { m_ext = ext; } + + // implement accessor functions + bool GetExtensions(wxArrayString& extensions); + bool GetMimeType(wxString *mimeType) const; + bool GetIcon(wxIcon *icon) const; + bool GetDescription(wxString *desc) const; + bool GetOpenCommand(wxString *openCmd, + const wxFileType::MessageParameters&) const + { return GetCommand(openCmd, "open"); } + bool GetPrintCommand(wxString *printCmd, + const wxFileType::MessageParameters&) const + { return GetCommand(printCmd, "print"); } + +private: + // helper function + bool GetCommand(wxString *command, const char *verb) const; + + wxString m_strFileType, m_ext; }; #else // Unix @@ -170,7 +257,7 @@ public: // * compose and composetyped fields are used to determine the program to be // called to create a new message pert in the specified format (unused). // -// Parameter/filename xpansion: +// Parameter/filename xpansion: // * %s is replaced with the (full) file name // * %t is replaced with MIME type/subtype of the entry // * for multipart type only %n is replaced with the nnumber of parts and %F is @@ -189,7 +276,7 @@ public: // comments. Each record has one of two following forms: // a) for "brief" format: // -// b) for "expanded" format: +// b) for "expanded" format: // type= \ desc="" \ exts="ext" // // We try to autodetect the format of mime.types: if a non-comment line starts @@ -220,9 +307,27 @@ public: // operations // prepend this element to the list void Prepend(MailCapEntry *next) { m_next = next; } - // append to the list + // insert into the list at given position + void Insert(MailCapEntry *next, size_t pos) + { + // FIXME slooow... + MailCapEntry *cur; + size_t n = 0; + for ( cur = next; cur != NULL; cur = cur->m_next, n++ ) { + if ( n == pos ) + break; + } + + wxASSERT_MSG( n == pos, wxT("invalid position in MailCapEntry::Insert") ); + + m_next = cur->m_next; + cur->m_next = this; + } + // append this element to the list void Append(MailCapEntry *next) { + wxCHECK_RET( next != NULL, wxT("Append()ing to what?") ); + // FIXME slooow... MailCapEntry *cur; for ( cur = next; cur->m_next != NULL; cur = cur->m_next ) @@ -230,9 +335,7 @@ public: cur->m_next = this; - // we initialize it in the ctor and there is no reason to both Prepend() - // and Append() one and the same entry - wxASSERT( m_next == NULL ); + wxASSERT_MSG( !m_next, wxT("Append()ing element already in the list?") ); } private: @@ -246,32 +349,100 @@ private: WX_DEFINE_ARRAY(MailCapEntry *, ArrayTypeEntries); +// the base class which may be used to find an icon for the MIME type +class wxMimeTypeIconHandler +{ +public: + virtual bool GetIcon(const wxString& mimetype, wxIcon *icon) = 0; +}; + +WX_DEFINE_ARRAY(wxMimeTypeIconHandler *, ArrayIconHandlers); + +// the icon handler which uses GNOME MIME database +class wxGNOMEIconHandler : public wxMimeTypeIconHandler +{ +public: + virtual bool GetIcon(const wxString& mimetype, wxIcon *icon); + +private: + void Init(); + void LoadIconsFromKeyFile(const wxString& filename); + void LoadKeyFilesFromDir(const wxString& dirbase); + + static bool m_inited; + + static wxSortedArrayString ms_mimetypes; + static wxArrayString ms_icons; +}; + +// the icon handler which uses KDE MIME database +class wxKDEIconHandler : public wxMimeTypeIconHandler +{ +public: + virtual bool GetIcon(const wxString& mimetype, wxIcon *icon); + +private: + void LoadLinksForMimeSubtype(const wxString& dirbase, + const wxString& subdir, + const wxString& filename); + void LoadLinksForMimeType(const wxString& dirbase, + const wxString& subdir); + void LoadLinkFilesFromDir(const wxString& dirbase); + void Init(); + + static bool m_inited; + + static wxSortedArrayString ms_mimetypes; + static wxArrayString ms_icons; +}; + +// this is the real wxMimeTypesManager for Unix class wxMimeTypesManagerImpl { friend class wxFileTypeImpl; // give it access to m_aXXX variables public: // ctor loads all info into memory for quicker access later on - // @@ it would be nice to load them all, but parse on demand only... + // TODO it would be nice to load them all, but parse on demand only... wxMimeTypesManagerImpl(); // implement containing class functions wxFileType *GetFileTypeFromExtension(const wxString& ext); wxFileType *GetFileTypeFromMimeType(const wxString& mimeType); - void ReadMailcap(const wxString& filename); - void ReadMimeTypes(const wxString& filename); + size_t EnumAllFileTypes(wxArrayString& mimetypes); + + bool ReadMailcap(const wxString& filename, bool fallback = FALSE); + bool ReadMimeTypes(const wxString& filename); + + void AddFallback(const wxFileTypeInfo& filetype); + + // add information about the given mimetype + void AddMimeTypeInfo(const wxString& mimetype, + const wxString& extensions, + const wxString& description); + void AddMailcapInfo(const wxString& strType, + const wxString& strOpenCmd, + const wxString& strPrintCmd, + const wxString& strTest, + const wxString& strDesc); // accessors // get the string containing space separated extensions for the given // file type wxString GetExtension(size_t index) { return m_aExtensions[index]; } + // get the array of icon handlers + static ArrayIconHandlers& GetIconHandlers(); + private: wxArrayString m_aTypes, // MIME types m_aDescriptions, // descriptions (just some text) m_aExtensions; // space separated list of extensions ArrayTypeEntries m_aEntries; // commands and tests for this file type + + // head of the linked list of the icon handlers + static ArrayIconHandlers ms_iconHandlers; }; class wxFileTypeImpl @@ -285,8 +456,7 @@ public: bool GetExtensions(wxArrayString& extensions); bool GetMimeType(wxString *mimeType) const { *mimeType = m_manager->m_aTypes[m_index]; return TRUE; } - bool GetIcon(wxIcon *icon) const - { return FALSE; } // @@ maybe with Gnome/KDE integration... + bool GetIcon(wxIcon *icon) const; bool GetDescription(wxString *desc) const { *desc = m_manager->m_aDescriptions[m_index]; return TRUE; } @@ -317,6 +487,42 @@ private: #endif // OS type +// ============================================================================ +// common classes +// ============================================================================ + +// ---------------------------------------------------------------------------- +// wxFileTypeInfo +// ---------------------------------------------------------------------------- + +wxFileTypeInfo::wxFileTypeInfo(const char *mimeType, + const char *openCmd, + const char *printCmd, + const char *desc, + ...) + : m_mimeType(mimeType), + m_openCmd(openCmd), + m_printCmd(printCmd), + m_desc(desc) +{ + va_list argptr; + va_start(argptr, desc); + + for ( ;; ) + { + const char *ext = va_arg(argptr, const char *); + if ( !ext ) + { + // NULL terminates the list + break; + } + + m_exts.Add(ext); + } + + va_end(argptr); +} + // ============================================================================ // implementation of the wrapper classes // ============================================================================ @@ -331,57 +537,57 @@ wxString wxFileType::ExpandCommand(const wxString& command, bool hasFilename = FALSE; wxString str; - for ( const char *pc = command.c_str(); *pc != '\0'; pc++ ) { - if ( *pc == '%' ) { + for ( const wxChar *pc = command.c_str(); *pc != wxT('\0'); pc++ ) { + if ( *pc == wxT('%') ) { switch ( *++pc ) { - case 's': + case wxT('s'): // '%s' expands into file name (quoted because it might // contain spaces) - except if there are already quotes - // there because otherwise some programs may get confused by - // double double quotes + // there because otherwise some programs may get confused + // by double double quotes #if 0 - if ( *(pc - 2) == '"' ) + if ( *(pc - 2) == wxT('"') ) str << params.GetFileName(); else - str << '"' << params.GetFileName() << '"'; + str << wxT('"') << params.GetFileName() << wxT('"'); #endif str << params.GetFileName(); hasFilename = TRUE; break; - case 't': + case wxT('t'): // '%t' expands into MIME type (quote it too just to be // consistent) - str << '\'' << params.GetMimeType() << '\''; + str << wxT('\'') << params.GetMimeType() << wxT('\''); break; - case '{': + case wxT('{'): { - const char *pEnd = strchr(pc, '}'); + const wxChar *pEnd = wxStrchr(pc, wxT('}')); if ( pEnd == NULL ) { wxString mimetype; wxLogWarning(_("Unmatched '{' in an entry for " "mime type %s."), params.GetMimeType().c_str()); - str << "%{"; + str << wxT("%{"); } else { wxString param(pc + 1, pEnd - pc - 1); - str << '\'' << params.GetParamValue(param) << '\''; + str << wxT('\'') << params.GetParamValue(param) << wxT('\''); pc = pEnd; } } break; - case 'n': - case 'F': + case wxT('n'): + case wxT('F'): // TODO %n is the number of parts, %F is an array containing // the names of temp files these parts were written to // and their mime types. break; default: - wxLogDebug("Unknown field %%%c in command '%s'.", + wxLogDebug(wxT("Unknown field %%%c in command '%s'."), *pc, command.c_str()); str << *pc; } @@ -394,7 +600,7 @@ wxString wxFileType::ExpandCommand(const wxString& command, // metamail(1) man page states that if the mailcap entry doesn't have '%s' // the program will accept the data on stdin: so give it to it! if ( !hasFilename && !str.IsEmpty() ) { - str << " < '" << params.GetFileName() << '\''; + str << wxT(" < '") << params.GetFileName() << wxT('\''); } return str; @@ -448,6 +654,28 @@ wxFileType::GetPrintCommand(wxString *printCmd, // wxMimeTypesManager // ---------------------------------------------------------------------------- +bool wxMimeTypesManager::IsOfType(const wxString& mimeType, + const wxString& wildcard) +{ + wxASSERT_MSG( mimeType.Find(wxT('*')) == wxNOT_FOUND, + wxT("first MIME type can't contain wildcards") ); + + // all comparaisons are case insensitive (2nd arg of IsSameAs() is FALSE) + if ( wildcard.BeforeFirst(wxT('/')).IsSameAs(mimeType.BeforeFirst(wxT('/')), FALSE) ) + { + wxString strSubtype = wildcard.AfterFirst(wxT('/')); + + if ( strSubtype == wxT("*") || + strSubtype.IsSameAs(mimeType.AfterFirst(wxT('/')), FALSE) ) + { + // matches (either exactly or it's a wildcard) + return TRUE; + } + } + + return FALSE; +} + wxMimeTypesManager::wxMimeTypesManager() { m_impl = new wxMimeTypesManagerImpl; @@ -470,54 +698,131 @@ wxMimeTypesManager::GetFileTypeFromMimeType(const wxString& mimeType) return m_impl->GetFileTypeFromMimeType(mimeType); } +bool wxMimeTypesManager::ReadMailcap(const wxString& filename, bool fallback) +{ + return m_impl->ReadMailcap(filename, fallback); +} + +bool wxMimeTypesManager::ReadMimeTypes(const wxString& filename) +{ + return m_impl->ReadMimeTypes(filename); +} + +void wxMimeTypesManager::AddFallbacks(const wxFileTypeInfo *filetypes) +{ + for ( const wxFileTypeInfo *ft = filetypes; ft->IsValid(); ft++ ) { + m_impl->AddFallback(*ft); + } +} + +size_t wxMimeTypesManager::EnumAllFileTypes(wxArrayString& mimetypes) +{ + return m_impl->EnumAllFileTypes(mimetypes); +} + // ============================================================================ // real (OS specific) implementation // ============================================================================ #ifdef __WXMSW__ -bool wxFileTypeImpl::GetCommand(wxString *command, const char *verb) const +wxString wxFileTypeImpl::GetCommand(const wxChar *verb) const { // suppress possible error messages wxLogNull nolog; wxString strKey; - strKey << m_strFileType << "\\shell\\" << verb << "\\command"; - wxRegKey key(wxRegKey::HKCR, strKey); + if ( wxRegKey(wxRegKey::HKCR, m_ext + _T("\\shell")).Exists() ) + strKey = m_ext; + if ( wxRegKey(wxRegKey::HKCR, m_strFileType + _T("\\shell")).Exists() ) + strKey = m_strFileType; + + if ( !strKey ) + { + // no info + return wxEmptyString; + } + + strKey << wxT("\\shell\\") << verb << wxT("\\command"); + wxRegKey key(wxRegKey::HKCR, strKey); + wxString command; if ( key.Open() ) { // it's the default value of the key - if ( key.QueryValue("", *command) ) { + if ( key.QueryValue(wxT(""), command) ) { // transform it from '%1' to '%s' style format string - // @@ we don't make any attempt to verify that the string is valid, - // i.e. doesn't contain %2, or second %1 or .... But we do make - // sure that we return a string with _exactly_ one '%s'! - size_t len = command->Len(); - for ( size_t n = 0; n < len; n++ ) { - if ( command->GetChar(n) == '%' && - (n + 1 < len) && command->GetChar(n + 1) == '1' ) { + + // NB: we don't make any attempt to verify that the string is valid, + // i.e. doesn't contain %2, or second %1 or .... But we do make + // sure that we return a string with _exactly_ one '%s'! + bool foundFilename = FALSE; + size_t len = command.Len(); + for ( size_t n = 0; (n < len) && !foundFilename; n++ ) { + if ( command[n] == wxT('%') && + (n + 1 < len) && command[n + 1] == wxT('1') ) { // replace it with '%s' - command->SetChar(n + 1, 's'); + command[n + 1] = wxT('s'); - return TRUE; + foundFilename = TRUE; } } - // we didn't find any '%1'! - // @@@ hack: append the filename at the end, hope that it will do - *command << " %s"; - - return TRUE; + if ( !foundFilename ) { + // we didn't find any '%1'! + // HACK: append the filename at the end, hope that it will do + command << wxT(" %s"); + } } } + //else: no such file type or no value, will return empty string - // no such file type or no value - return FALSE; + return command; } -// @@ this function is half implemented +bool +wxFileTypeImpl::GetOpenCommand(wxString *openCmd, + const wxFileType::MessageParameters& params) + const +{ + wxString cmd; + if ( m_info ) { + cmd = m_info->GetOpenCommand(); + } + else { + cmd = GetCommand(wxT("open")); + } + + *openCmd = wxFileType::ExpandCommand(cmd, params); + + return !openCmd->IsEmpty(); +} + +bool +wxFileTypeImpl::GetPrintCommand(wxString *printCmd, + const wxFileType::MessageParameters& params) + const +{ + wxString cmd; + if ( m_info ) { + cmd = m_info->GetPrintCommand(); + } + else { + cmd = GetCommand(wxT("print")); + } + + *printCmd = wxFileType::ExpandCommand(cmd, params); + + return !printCmd->IsEmpty(); +} + +// TODO this function is half implemented bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) { - if ( m_ext.IsEmpty() ) { + if ( m_info ) { + extensions = m_info->GetExtensions(); + + return TRUE; + } + else if ( m_ext.IsEmpty() ) { // the only way to get the list of extensions from the file type is to // scan through all extensions in the registry - too slow... return FALSE; @@ -533,10 +838,17 @@ bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const { + if ( m_info ) { + // we already have it + *mimeType = m_info->GetMimeType(); + + return TRUE; + } + // suppress possible error messages wxLogNull nolog; - wxRegKey key(wxRegKey::HKCR, m_strFileType); - if ( key.Open() && key.QueryValue("Content Type", *mimeType) ) { + wxRegKey key(wxRegKey::HKCR, wxT(".") + m_ext); + if ( key.Open() && key.QueryValue(wxT("Content Type"), *mimeType) ) { return TRUE; } else { @@ -546,8 +858,14 @@ bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const bool wxFileTypeImpl::GetIcon(wxIcon *icon) const { +#if wxUSE_GUI + if ( m_info ) { + // we don't have icons in the fallback resources + return FALSE; + } + wxString strIconKey; - strIconKey << m_strFileType << "\\DefaultIcon"; + strIconKey << m_strFileType << wxT("\\DefaultIcon"); // suppress possible error messages wxLogNull nolog; @@ -556,28 +874,28 @@ bool wxFileTypeImpl::GetIcon(wxIcon *icon) const if ( key.Open() ) { wxString strIcon; // it's the default value of the key - if ( key.QueryValue("", strIcon) ) { + if ( key.QueryValue(wxT(""), strIcon) ) { // the format is the following: , // NB: icon index may be negative as well as positive and the full // path may contain the environment variables inside '%' - wxString strFullPath = strIcon.BeforeLast(','), - strIndex = strIcon.AfterLast(','); + wxString strFullPath = strIcon.BeforeLast(wxT(',')), + strIndex = strIcon.AfterLast(wxT(',')); // index may be omitted, in which case BeforeLast(',') is empty and // AfterLast(',') is the whole string if ( strFullPath.IsEmpty() ) { strFullPath = strIndex; - strIndex = "0"; + strIndex = wxT("0"); } wxString strExpPath = wxExpandEnvVars(strFullPath); - int nIndex = atoi(strIndex); + int nIndex = wxAtoi(strIndex); HICON hIcon = ExtractIcon(GetModuleHandle(NULL), strExpPath, nIndex); switch ( (int)hIcon ) { case 0: // means no icons were found case 1: // means no such file or it wasn't a DLL/EXE/OCX/ICO/... - wxLogDebug("incorrect registry entry '%s': no such icon.", + wxLogDebug(wxT("incorrect registry entry '%s': no such icon."), key.GetName().c_str()); break; @@ -589,18 +907,27 @@ bool wxFileTypeImpl::GetIcon(wxIcon *icon) const } // no such file type or no value or incorrect icon entry +#endif // wxUSE_GUI + return FALSE; } bool wxFileTypeImpl::GetDescription(wxString *desc) const { + if ( m_info ) { + // we already have it + *desc = m_info->GetDescription(); + + return TRUE; + } + // suppress possible error messages wxLogNull nolog; wxRegKey key(wxRegKey::HKCR, m_strFileType); if ( key.Open() ) { // it's the default value of the key - if ( key.QueryValue("", *desc) ) { + if ( key.QueryValue(wxT(""), *desc) ) { return TRUE; } } @@ -614,41 +941,67 @@ wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext) { // add the leading point if necessary wxString str; - if ( ext[0u] != '.' ) { - str = '.'; + if ( ext[0u] != wxT('.') ) { + str = wxT('.'); } str << ext; // suppress possible error messages wxLogNull nolog; + bool knownExtension = FALSE; + wxString strFileType; wxRegKey key(wxRegKey::HKCR, str); if ( key.Open() ) { // it's the default value of the key - if ( key.QueryValue("", strFileType) ) { + if ( key.QueryValue(wxT(""), strFileType) ) { // create the new wxFileType object wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType(strFileType); - fileType->m_impl->SetExt(ext); + fileType->m_impl->Init(strFileType, ext); return fileType; } + else { + // this extension doesn't have a filetype, but it's known to the + // system and may be has some other useful keys (open command or + // content-type), so still return a file type object for it + knownExtension = TRUE; + } } - // unknown extension - return NULL; + // check the fallbacks + // TODO linear search is potentially slow, perhaps we should use a sorted + // array? + size_t count = m_fallbacks.GetCount(); + for ( size_t n = 0; n < count; n++ ) { + if ( m_fallbacks[n].GetExtensions().Index(ext) != wxNOT_FOUND ) { + wxFileType *fileType = new wxFileType; + fileType->m_impl->Init(m_fallbacks[n]); + + return fileType; + } + } + + if ( knownExtension ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->Init(wxEmptyString, ext); + + return fileType; + } + else + { + // unknown extension + return NULL; + } } // MIME type -> extension -> file type wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) { - // @@@ I don't know of any official documentation which mentions this - // location, but as a matter of fact IE uses it, so why not we? - static const char *szMimeDbase = "MIME\\Database\\Content Type\\"; - - wxString strKey = szMimeDbase; + wxString strKey = MIME_DATABASE_KEY; strKey << mimeType; // suppress possible error messages @@ -657,49 +1010,600 @@ wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) wxString ext; wxRegKey key(wxRegKey::HKCR, strKey); if ( key.Open() ) { - if ( key.QueryValue("Extension", ext) ) { + if ( key.QueryValue(wxT("Extension"), ext) ) { return GetFileTypeFromExtension(ext); } } + // check the fallbacks + // TODO linear search is potentially slow, perhaps we should use a sorted + // array? + size_t count = m_fallbacks.GetCount(); + for ( size_t n = 0; n < count; n++ ) { + if ( wxMimeTypesManager::IsOfType(mimeType, + m_fallbacks[n].GetMimeType()) ) { + wxFileType *fileType = new wxFileType; + fileType->m_impl->Init(m_fallbacks[n]); + + return fileType; + } + } + // unknown MIME type return NULL; } -#else // Unix - -MailCapEntry * -wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const +size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes) { - wxString command; - MailCapEntry *entry = m_manager->m_aEntries[m_index]; - while ( entry != NULL ) { - // notice that an empty command would always succeed (@@ is it ok?) - command = wxFileType::ExpandCommand(entry->GetTestCmd(), params); + // enumerate all keys under MIME_DATABASE_KEY + wxRegKey key(wxRegKey::HKCR, MIME_DATABASE_KEY); - if ( command.IsEmpty() || (system(command) == 0) ) { - // ok, passed - wxLogTrace("Test '%s' for mime type '%s' succeeded.", - command.c_str(), params.GetMimeType().c_str()); - break; - } - else { - wxLogTrace("Test '%s' for mime type '%s' failed.", - command.c_str(), params.GetMimeType().c_str()); - } + wxString type; + long cookie; + bool cont = key.GetFirstKey(type, cookie); + while ( cont ) + { + mimetypes.Add(type); - entry = entry->GetNext(); + cont = key.GetNextKey(type, cookie); } - return entry; + return mimetypes.GetCount(); } -bool -wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd, - const wxFileType::MessageParameters& params, - bool open) const +#elif defined ( __WXMAC__ ) + +bool wxFileTypeImpl::GetCommand(wxString *command, const char *verb) const { - MailCapEntry *entry = GetEntry(params); + return FALSE; +} + +// @@ this function is half implemented +bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) +{ + return FALSE; +} + +bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const +{ + if ( m_strFileType.Length() > 0 ) + { + *mimeType = m_strFileType ; + return TRUE ; + } + else + return FALSE; +} + +bool wxFileTypeImpl::GetIcon(wxIcon *icon) const +{ + // no such file type or no value or incorrect icon entry + return FALSE; +} + +bool wxFileTypeImpl::GetDescription(wxString *desc) const +{ + return FALSE; +} + +// extension -> file type +wxFileType * +wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& e) +{ + wxString ext = e ; + ext = ext.Lower() ; + if ( ext == "txt" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("text/text"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "htm" || ext == "html" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("text/html"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "gif" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("image/gif"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "png" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("image/png"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "jpg" || ext == "jpeg" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("image/jpeg"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "bmp" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("image/bmp"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "tif" || ext == "tiff" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("image/tiff"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "xpm" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("image/xpm"); + fileType->m_impl->SetExt(ext); + return fileType; + } + else if ( ext == "xbm" ) + { + wxFileType *fileType = new wxFileType; + fileType->m_impl->SetFileType("image/xbm"); + fileType->m_impl->SetExt(ext); + return fileType; + } + + // unknown extension + return NULL; +} + +// MIME type -> extension -> file type +wxFileType * +wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) +{ + return NULL; +} + +size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes) +{ + wxFAIL_MSG( _T("TODO") ); // VZ: don't know anything about this for Mac + + return 0; +} + +#else // Unix + +// ============================================================================ +// Unix implementation +// ============================================================================ + +// ---------------------------------------------------------------------------- +// various statics +// ---------------------------------------------------------------------------- + +static wxGNOMEIconHandler gs_iconHandlerGNOME; +static wxKDEIconHandler gs_iconHandlerKDE; + +bool wxGNOMEIconHandler::m_inited = FALSE; +wxSortedArrayString wxGNOMEIconHandler::ms_mimetypes; +wxArrayString wxGNOMEIconHandler::ms_icons; + +bool wxKDEIconHandler::m_inited = FALSE; +wxSortedArrayString wxKDEIconHandler::ms_mimetypes; +wxArrayString wxKDEIconHandler::ms_icons; + +ArrayIconHandlers wxMimeTypesManagerImpl::ms_iconHandlers; + +// ---------------------------------------------------------------------------- +// wxGNOMEIconHandler +// ---------------------------------------------------------------------------- + +// GNOME stores the info we're interested in in several locations: +// 1. xxx.keys files under /usr/share/mime-info +// 2. xxx.keys files under ~/.gnome/mime-info +// +// The format of xxx.keys file is the following: +// +// mimetype/subtype: +// field=value +// +// with blank lines separating the entries and indented lines starting with +// TABs. We're interested in the field icon-filename whose value is the path +// containing the icon. + +void wxGNOMEIconHandler::LoadIconsFromKeyFile(const wxString& filename) +{ + wxTextFile textfile(filename); + if ( !textfile.Open() ) + return; + + // values for the entry being parsed + wxString curMimeType, curIconFile; + + const wxChar *pc; + size_t nLineCount = textfile.GetLineCount(); + for ( size_t nLine = 0; ; nLine++ ) + { + if ( nLine < nLineCount ) + { + pc = textfile[nLine].c_str(); + if ( *pc == _T('#') ) + { + // skip comments + continue; + } + } + else + { + // so that we will fall into the "if" below + pc = NULL; + } + + if ( !pc || !*pc ) + { + // end of the entry + if ( !!curMimeType && !!curIconFile ) + { + // do we already know this mimetype? + int i = ms_mimetypes.Index(curMimeType); + if ( i == wxNOT_FOUND ) + { + // add a new entry + size_t n = ms_mimetypes.Add(curMimeType); + ms_icons.Insert(curIconFile, n); + } + else + { + // replace the existing one (this means that the directories + // should be searched in order of increased priority!) + ms_icons[(size_t)i] = curIconFile; + } + } + + if ( !pc ) + { + // the end - this can only happen if nLine == nLineCount + break; + } + + curIconFile.Empty(); + + continue; + } + + // what do we have here? + if ( *pc == _T('\t') ) + { + // this is a field=value ling + pc++; // skip leading TAB + + static const int lenField = 13; // strlen("icon-filename") + if ( wxStrncmp(pc, _T("icon-filename"), lenField) == 0 ) + { + // skip '=' which follows and take everything left until the end + // of line + curIconFile = pc + lenField + 1; + } + //else: some other field, we don't care + } + else + { + // this is the start of the new section + curMimeType.Empty(); + + while ( *pc != _T(':') && *pc != _T('\0') ) + { + curMimeType += *pc++; + } + + if ( !*pc ) + { + // we reached the end of line without finding the colon, + // something is wrong - ignore this line completely + wxLogDebug(_T("Unreckognized line %d in file '%s' ignored"), + nLine + 1, filename.c_str()); + + break; + } + } + } +} + +void wxGNOMEIconHandler::LoadKeyFilesFromDir(const wxString& dirbase) +{ + wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase), + _T("base directory shouldn't end with a slash") ); + + wxString dirname = dirbase; + dirname << _T("/mime-info"); + + if ( !wxDir::Exists(dirname) ) + return; + + wxDir dir(dirname); + if ( !dir.IsOpened() ) + return; + + // we will concatenate it with filename to get the full path below + dirname += _T('/'); + + wxString filename; + bool cont = dir.GetFirst(&filename, _T("*.keys"), wxDIR_FILES); + while ( cont ) + { + LoadIconsFromKeyFile(dirname + filename); + + cont = dir.GetNext(&filename); + } +} + +void wxGNOMEIconHandler::Init() +{ + wxArrayString dirs; + dirs.Add(_T("/usr/share")); + + wxString gnomedir; + wxGetHomeDir( &gnomedir ); + gnomedir += _T("/.gnome"); + dirs.Add( gnomedir ); + + size_t nDirs = dirs.GetCount(); + for ( size_t nDir = 0; nDir < nDirs; nDir++ ) + { + LoadKeyFilesFromDir(dirs[nDir]); + } + + m_inited = TRUE; +} + +bool wxGNOMEIconHandler::GetIcon(const wxString& mimetype, wxIcon *icon) +{ + if ( !m_inited ) + { + Init(); + } + + int index = ms_mimetypes.Index(mimetype); + if ( index == wxNOT_FOUND ) + return FALSE; + + wxString iconname = ms_icons[(size_t)index]; + +#if wxUSE_GUI + *icon = wxIcon(iconname); +#else + // helpful for testing in console mode + wxLogDebug(_T("Found GNOME icon for '%s': '%s'\n"), + mimetype.c_str(), iconname.c_str()); +#endif + + return TRUE; +} + +// ---------------------------------------------------------------------------- +// wxKDEIconHandler +// ---------------------------------------------------------------------------- + +// KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype +// may be found in either of the following locations +// +// 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk +// 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk +// +// The format of a .kdelnk file is almost the same as the one used by +// wxFileConfig, i.e. there are groups, comments and entries. The icon is the +// value for the entry "Type" + +void wxKDEIconHandler::LoadLinksForMimeSubtype(const wxString& dirbase, + const wxString& subdir, + const wxString& filename) +{ + wxFFile file(dirbase + filename); + if ( !file.IsOpened() ) + return; + + // these files are small, slurp the entire file at once + wxString text; + if ( !file.ReadAll(&text) ) + return; + + int pos = text.Find(_T("Icon=")); + if ( pos == wxNOT_FOUND ) + { + // no icon info + return; + } + + wxString icon; + + const wxChar *pc = text.c_str() + pos + 5; // 5 == strlen("Icon=") + while ( *pc && *pc != _T('\n') ) + { + icon += *pc++; + } + + if ( !!icon ) + { + // don't check that the file actually exists - would be too slow + icon.Prepend(_T("/usr/share/icons/")); + + // construct mimetype from the directory name and the basename of the + // file (it always has .kdelnk extension) + wxString mimetype; + mimetype << subdir << _T('/') << filename.BeforeLast(_T('.')); + + // do we already have this MIME type? + int i = ms_mimetypes.Index(mimetype); + if ( i == wxNOT_FOUND ) + { + // add it + size_t n = ms_mimetypes.Add(mimetype); + ms_icons.Insert(icon, n); + } + else + { + // replace the old value + ms_icons[(size_t)i] = icon; + } + } +} + +void wxKDEIconHandler::LoadLinksForMimeType(const wxString& dirbase, + const wxString& subdir) +{ + wxString dirname = dirbase; + dirname += subdir; + wxDir dir(dirname); + if ( !dir.IsOpened() ) + return; + + dirname += _T('/'); + + wxString filename; + bool cont = dir.GetFirst(&filename, _T("*.kdelnk"), wxDIR_FILES); + while ( cont ) + { + LoadLinksForMimeSubtype(dirname, subdir, filename); + + cont = dir.GetNext(&filename); + } +} + +void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString& dirbase) +{ + wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase), + _T("base directory shouldn't end with a slash") ); + + wxString dirname = dirbase; + dirname << _T("/mimelnk"); + + if ( !wxDir::Exists(dirname) ) + return; + + wxDir dir(dirname); + if ( !dir.IsOpened() ) + return; + + // we will concatenate it with dir name to get the full path below + dirname += _T('/'); + + wxString subdir; + bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS); + while ( cont ) + { + LoadLinksForMimeType(dirname, subdir); + + cont = dir.GetNext(&subdir); + } +} + +void wxKDEIconHandler::Init() +{ + wxArrayString dirs; + + // the variable KDEDIR is set when KDE is running + const char *kdedir = getenv("KDEDIR"); + if ( kdedir ) + { + dirs.Add(wxString(kdedir) + _T("/share")); + } + else + { + // try to guess KDEDIR + dirs.Add(_T("/usr/share")); + dirs.Add(_T("/opt/kde/share")); + } + + dirs.Add(wxGetHomeDir() + _T("/.kde/share")); + + size_t nDirs = dirs.GetCount(); + for ( size_t nDir = 0; nDir < nDirs; nDir++ ) + { + LoadLinkFilesFromDir(dirs[nDir]); + } + + m_inited = TRUE; +} + +bool wxKDEIconHandler::GetIcon(const wxString& mimetype, wxIcon *icon) +{ + if ( !m_inited ) + { + Init(); + } + + int index = ms_mimetypes.Index(mimetype); + if ( index == wxNOT_FOUND ) + return FALSE; + + wxString iconname = ms_icons[(size_t)index]; + +#if wxUSE_GUI + *icon = wxIcon(iconname); +#else + // helpful for testing in console mode + wxLogDebug(_T("Found KDE icon for '%s': '%s'\n"), + mimetype.c_str(), iconname.c_str()); +#endif + + return TRUE; +} + +// ---------------------------------------------------------------------------- +// wxFileTypeImpl (Unix) +// ---------------------------------------------------------------------------- + +MailCapEntry * +wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const +{ + wxString command; + MailCapEntry *entry = m_manager->m_aEntries[m_index]; + while ( entry != NULL ) { + // notice that an empty command would always succeed (it's ok) + command = wxFileType::ExpandCommand(entry->GetTestCmd(), params); + + if ( command.IsEmpty() || (wxSystem(command) == 0) ) { + // ok, passed + wxLogTrace(wxT("Test '%s' for mime type '%s' succeeded."), + command.c_str(), params.GetMimeType().c_str()); + break; + } + else { + wxLogTrace(wxT("Test '%s' for mime type '%s' failed."), + command.c_str(), params.GetMimeType().c_str()); + } + + entry = entry->GetNext(); + } + + return entry; +} + +bool wxFileTypeImpl::GetIcon(wxIcon *icon) const +{ + wxString mimetype; + (void)GetMimeType(&mimetype); + + ArrayIconHandlers& handlers = m_manager->GetIconHandlers(); + size_t count = handlers.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + if ( handlers[n]->GetIcon(mimetype, icon) ) + return TRUE; + } + + return FALSE; +} + +bool +wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd, + const wxFileType::MessageParameters& params, + bool open) const +{ + MailCapEntry *entry = GetEntry(params); if ( entry == NULL ) { // all tests failed... return FALSE; @@ -722,8 +1626,8 @@ bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) // one extension in the space or comma delimitid list wxString strExt; - for ( const char *p = strExtensions; ; p++ ) { - if ( *p == ' ' || *p == ',' || *p == '\0' ) { + for ( const wxChar *p = strExtensions; ; p++ ) { + if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') ) { if ( !strExt.IsEmpty() ) { extensions.Add(strExt); strExt.Empty(); @@ -731,13 +1635,13 @@ bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) //else: repeated spaces (shouldn't happen, but it's not that // important if it does happen) - if ( *p == '\0' ) + if ( *p == wxT('\0') ) break; } - else if ( *p == '.' ) { + else if ( *p == wxT('.') ) { // remove the dot from extension (but only if it's the first char) if ( !strExt.IsEmpty() ) { - strExt += '.'; + strExt += wxT('.'); } //else: no, don't append it } @@ -749,45 +1653,61 @@ bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) return TRUE; } +// ---------------------------------------------------------------------------- +// wxMimeTypesManagerImpl (Unix) +// ---------------------------------------------------------------------------- + +/* static */ +ArrayIconHandlers& wxMimeTypesManagerImpl::GetIconHandlers() +{ + if ( ms_iconHandlers.GetCount() == 0 ) + { + ms_iconHandlers.Add(&gs_iconHandlerGNOME); + ms_iconHandlers.Add(&gs_iconHandlerKDE); + } + + return ms_iconHandlers; +} + // read system and user mailcaps (TODO implement mime.types support) wxMimeTypesManagerImpl::wxMimeTypesManagerImpl() { // directories where we look for mailcap and mime.types by default // (taken from metamail(1) sources) - static const char *aStandardLocations[] = + static const wxChar *aStandardLocations[] = { - "/etc", - "/usr/etc", - "/usr/local/etc", - "/etc/mail", - "/usr/public/lib" + wxT("/etc"), + wxT("/usr/etc"), + wxT("/usr/local/etc"), + wxT("/etc/mail"), + wxT("/usr/public/lib") }; // first read the system wide file(s) for ( size_t n = 0; n < WXSIZEOF(aStandardLocations); n++ ) { wxString dir = aStandardLocations[n]; - wxString file = dir + "/mailcap"; + wxString file = dir + wxT("/mailcap"); if ( wxFile::Exists(file) ) { ReadMailcap(file); } - file = dir + "/mime.types"; + file = dir + wxT("/mime.types"); if ( wxFile::Exists(file) ) { ReadMimeTypes(file); } } - wxString strHome = getenv("HOME"); + wxString strHome = wxGetenv(wxT("HOME")); // and now the users mailcap - wxString strUserMailcap = strHome + "/.mailcap"; + wxString strUserMailcap = strHome + wxT("/.mailcap"); if ( wxFile::Exists(strUserMailcap) ) { ReadMailcap(strUserMailcap); } // read the users mime.types - wxString strUserMimeTypes = strHome + "/.mime.types"; + wxString strUserMimeTypes = strHome + wxT("/.mime.types"); if ( wxFile::Exists(strUserMimeTypes) ) { ReadMimeTypes(strUserMimeTypes); } @@ -800,15 +1720,15 @@ wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext) for ( size_t n = 0; n < count; n++ ) { wxString extensions = m_aExtensions[n]; while ( !extensions.IsEmpty() ) { - wxString field = extensions.BeforeFirst(' '); - extensions = extensions.AfterFirst(' '); + wxString field = extensions.BeforeFirst(wxT(' ')); + extensions = extensions.AfterFirst(wxT(' ')); // consider extensions as not being case-sensitive - if ( field.IsSameAs(ext, FALSE /* no case */) ) { + if ( field.IsSameAs(ext, FALSE /* no case */) ) { // found wxFileType *fileType = new wxFileType; fileType->m_impl->Init(this, n); - + return fileType; } } @@ -831,12 +1751,12 @@ wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) // then try to find "text/*" as match for "text/plain" (for example) // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return // the whole string - ok. - wxString strCategory = mimetype.BeforeFirst('/'); + wxString strCategory = mimetype.BeforeFirst(wxT('/')); size_t nCount = m_aTypes.Count(); for ( size_t n = 0; n < nCount; n++ ) { - if ( (m_aTypes[n].BeforeFirst('/') == strCategory ) && - m_aTypes[n].AfterFirst('/') == "*" ) { + if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) && + m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") ) { index = n; break; } @@ -855,43 +1775,125 @@ wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) } } -void wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName) +void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype) +{ + wxString extensions; + const wxArrayString& exts = filetype.GetExtensions(); + size_t nExts = exts.GetCount(); + for ( size_t nExt = 0; nExt < nExts; nExt++ ) { + if ( nExt > 0 ) { + extensions += wxT(' '); + } + extensions += exts[nExt]; + } + + AddMimeTypeInfo(filetype.GetMimeType(), + extensions, + filetype.GetDescription()); + + AddMailcapInfo(filetype.GetMimeType(), + filetype.GetOpenCommand(), + filetype.GetPrintCommand(), + wxT(""), + filetype.GetDescription()); +} + +void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType, + const wxString& strExtensions, + const wxString& strDesc) +{ + int index = m_aTypes.Index(strMimeType); + if ( index == wxNOT_FOUND ) { + // add a new entry + m_aTypes.Add(strMimeType); + m_aEntries.Add(NULL); + m_aExtensions.Add(strExtensions); + m_aDescriptions.Add(strDesc); + } + else { + // modify an existing one + if ( !strDesc.IsEmpty() ) { + m_aDescriptions[index] = strDesc; // replace old value + } + m_aExtensions[index] += ' ' + strExtensions; + } +} + +void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType, + const wxString& strOpenCmd, + const wxString& strPrintCmd, + const wxString& strTest, + const wxString& strDesc) +{ + MailCapEntry *entry = new MailCapEntry(strOpenCmd, strPrintCmd, strTest); + + int nIndex = m_aTypes.Index(strType); + if ( nIndex == wxNOT_FOUND ) { + // new file type + m_aTypes.Add(strType); + + m_aEntries.Add(entry); + m_aExtensions.Add(wxT("")); + m_aDescriptions.Add(strDesc); + } + else { + // always append the entry in the tail of the list - info added with + // this function can only come from AddFallbacks() + MailCapEntry *entryOld = m_aEntries[nIndex]; + if ( entryOld ) + entry->Append(entryOld); + else + m_aEntries[nIndex] = entry; + } +} + +bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName) { - wxLogTrace("--- Parsing mime.types file '%s' ---", strFileName.c_str()); + wxLogTrace(wxT("--- Parsing mime.types file '%s' ---"), strFileName.c_str()); wxTextFile file(strFileName); if ( !file.Open() ) - return; + return FALSE; // the information we extract wxString strMimeType, strDesc, strExtensions; size_t nLineCount = file.GetLineCount(); + const wxChar *pc = NULL; for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) { - // now we're at the start of the line - const char *pc = file[nLine].c_str(); + if ( pc == NULL ) { + // now we're at the start of the line + pc = file[nLine].c_str(); + } + else { + // we didn't finish with the previous line yet + nLine--; + } // skip whitespace - while ( isspace(*pc) ) + while ( wxIsspace(*pc) ) pc++; - // comment? - if ( *pc == '#' ) + // comment or blank line? + if ( *pc == wxT('#') || !*pc ) { + // skip the whole line + pc = NULL; continue; + } // detect file format - const char *pEqualSign = strchr(pc, '='); + const wxChar *pEqualSign = wxStrchr(pc, wxT('=')); if ( pEqualSign == NULL ) { // brief format // ------------ // first field is mime type - for ( strMimeType.Empty(); !isspace(*pc) && *pc != '\0'; pc++ ) { + for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ ) { strMimeType += *pc; } // skip whitespace - while ( isspace(*pc) ) + while ( wxIsspace(*pc) ) pc++; // take all the rest of the string @@ -908,13 +1910,13 @@ void wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName) wxString strLHS(pc, pEqualSign - pc); // eat whitespace - for ( pc = pEqualSign + 1; isspace(*pc); pc++ ) + for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ ) ; - const char *pEnd; - if ( *pc == '"' ) { + const wxChar *pEnd; + if ( *pc == wxT('"') ) { // the string is quoted and ends at the matching quote - pEnd = strchr(++pc, '"'); + pEnd = wxStrchr(++pc, wxT('"')); if ( pEnd == NULL ) { wxLogWarning(_("Mime.types file %s, line %d: unterminated " "quoted string."), @@ -922,41 +1924,39 @@ void wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName) } } else { - // unquoted stringends at the first space - for ( pEnd = pc; !isspace(*pEnd); pEnd++ ) + // unquoted string ends at the first space + for ( pEnd = pc; !wxIsspace(*pEnd); pEnd++ ) ; } // now we have the RHS (field value) wxString strRHS(pc, pEnd - pc); - // check that it's more or less what we're waiting for, i.e. that - // only '\' is left on the line - if ( *pEnd == '"' ) { + // check what follows this entry + if ( *pEnd == wxT('"') ) { // skip this quote pEnd++; } - for ( pc = pEnd; isspace(*pc); pc++ ) + for ( pc = pEnd; wxIsspace(*pc); pc++ ) ; - // only '\\' may be left on the line normally - bool entryEnded = *pc == '\0'; - if ( !entryEnded && ((*pc != '\\') || (*++pc != '\0')) ) { - wxLogWarning(_("Mime.types file %s, line %d: extra characters " - "after the field value ignored."), - strFileName.c_str(), nLine + 1); + // if there is something left, it may be either a '\\' to continue + // the line or the next field of the same entry + bool entryEnded = *pc == wxT('\0'), + nextFieldOnSameLine = FALSE; + if ( !entryEnded ) { + nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0'))); } - // if there is a trailing backslash entryEnded = FALSE // now see what we got - if ( strLHS == "type" ) { + if ( strLHS == wxT("type") ) { strMimeType = strRHS; } - else if ( strLHS == "desc" ) { + else if ( strLHS == wxT("desc") ) { strDesc = strRHS; } - else if ( strLHS == "exts" ) { + else if ( strLHS == wxT("exts") ) { strExtensions = strRHS; } else { @@ -965,57 +1965,70 @@ void wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName) } if ( !entryEnded ) { - // as we don't reset strMimeType, the next line in this entry + if ( !nextFieldOnSameLine ) + pc = NULL; + //else: don't reset it + + // as we don't reset strMimeType, the next field in this entry // will be interpreted correctly. + continue; } } - int index = m_aTypes.Index(strMimeType); - if ( index == wxNOT_FOUND ) { - // add a new entry - m_aTypes.Add(strMimeType); - m_aEntries.Add(NULL); - m_aExtensions.Add(strExtensions); - m_aDescriptions.Add(strDesc); - } - else { - // modify an existing one - if ( !strDesc.IsEmpty() ) { - m_aDescriptions[index] = strDesc; // replace old value - } - m_aExtensions[index] += strExtensions; + // although it doesn't seem to be covered by RFCs, some programs + // (notably Netscape) create their entries with several comma + // separated extensions (RFC mention the spaces only) + strExtensions.Replace(wxT(","), wxT(" ")); + + // also deal with the leading dot + if ( !strExtensions.IsEmpty() && strExtensions[0u] == wxT('.') ) + { + strExtensions.erase(0, 1); } + + AddMimeTypeInfo(strMimeType, strExtensions, strDesc); + + // finished with this line + pc = NULL; } // check our data integriry wxASSERT( m_aTypes.Count() == m_aEntries.Count() && m_aTypes.Count() == m_aExtensions.Count() && m_aTypes.Count() == m_aDescriptions.Count() ); + + return TRUE; } -void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) +bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName, + bool fallback) { - wxLogTrace("--- Parsing mailcap file '%s' ---", strFileName.c_str()); + wxLogTrace(wxT("--- Parsing mailcap file '%s' ---"), strFileName.c_str()); wxTextFile file(strFileName); if ( !file.Open() ) - return; + return FALSE; - // see the comments near the end of function for the reason we need this + // see the comments near the end of function for the reason we need these + // variables (search for the next occurence of them) + // indices of MIME types (in m_aTypes) we already found in this file wxArrayInt aEntryIndices; + // aLastIndices[n] is the index of last element in + // m_aEntries[aEntryIndices[n]] from this file + wxArrayInt aLastIndices; size_t nLineCount = file.GetLineCount(); for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) { // now we're at the start of the line - const char *pc = file[nLine].c_str(); + const wxChar *pc = file[nLine].c_str(); // skip whitespace - while ( isspace(*pc) ) + while ( wxIsspace(*pc) ) pc++; // comment or empty string? - if ( *pc == '#' || *pc == '\0' ) + if ( *pc == wxT('#') || *pc == wxT('\0') ) continue; // no, do parse @@ -1031,8 +2044,8 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) } currentToken = Field_Type; // the flags and field values on the current line - bool needsterminal = false, - copiousoutput = false; + bool needsterminal = FALSE, + copiousoutput = FALSE; wxString strType, strOpenCmd, strPrintCmd, @@ -1041,10 +2054,10 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) curField; // accumulator for ( bool cont = TRUE; cont; pc++ ) { switch ( *pc ) { - case '\\': + case wxT('\\'): // interpret the next character literally (notice that // backslash can be used for line continuation) - if ( *++pc == '\0' ) { + if ( *++pc == wxT('\0') ) { // fetch the next line. // pc currently points to nowhere, but after the next @@ -1058,12 +2071,12 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) } break; - case '\0': + case wxT('\0'): cont = FALSE; // end of line reached, exit the loop // fall through - case ';': + case wxT(';'): // store this field and start looking for the next one // trim whitespaces from both sides @@ -1072,9 +2085,9 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) switch ( currentToken ) { case Field_Type: strType = curField; - if ( strType.Find('/') == wxNOT_FOUND ) { + if ( strType.Find(wxT('/')) == wxNOT_FOUND ) { // we interpret "type" as "type/*" - strType += "/*"; + strType += wxT("/*"); } currentToken = Field_OpenCmd; @@ -1092,22 +2105,22 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) bool ok = TRUE; // is this something of the form foo=bar? - const char *pEq = strchr(curField, '='); + const wxChar *pEq = wxStrchr(curField, wxT('=')); if ( pEq != NULL ) { - wxString lhs = curField.Before('='), - rhs = curField.After('='); + wxString lhs = curField.BeforeFirst(wxT('=')), + rhs = curField.AfterFirst(wxT('=')); lhs.Trim(TRUE); // from right rhs.Trim(FALSE); // from left - if ( lhs == "print" ) + if ( lhs == wxT("print") ) strPrintCmd = rhs; - else if ( lhs == "test" ) + else if ( lhs == wxT("test") ) strTest = rhs; - else if ( lhs == "description" ) { + else if ( lhs == wxT("description") ) { // it might be quoted - if ( rhs[0u] == '"' && - rhs.Last() == '"' ) { + if ( rhs[0u] == wxT('"') && + rhs.Last() == wxT('"') ) { strDesc = wxString(rhs.c_str() + 1, rhs.Len() - 2); } @@ -1115,10 +2128,10 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) strDesc = rhs; } } - else if ( lhs == "compose" || - lhs == "composetyped" || - lhs == "notes" || - lhs == "edit" ) + else if ( lhs == wxT("compose") || + lhs == wxT("composetyped") || + lhs == wxT("notes") || + lhs == wxT("edit") ) ; // ignore else ok = FALSE; @@ -1129,11 +2142,11 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) // TODO support the flags: // 1. create an xterm for 'needsterminal' // 2. append "| $PAGER" for 'copiousoutput' - if ( curField == "needsterminal" ) + if ( curField == wxT("needsterminal") ) needsterminal = TRUE; - else if ( curField == "copiousoutput" ) + else if ( curField == wxT("copiousoutput") ) copiousoutput = TRUE; - else if ( curField == "textualnewlines" ) + else if ( curField == wxT("textualnewlines") ) ; // ignore else ok = FALSE; @@ -1141,19 +2154,27 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) if ( !ok ) { - // don't flood the user with error messages - // if we don't understand something in his - // mailcap - wxLogDebug - ( - _("Mailcap file %s, line %d: unknown " - "field '%s' for the MIME type " - "'%s' ignored."), - strFileName.c_str(), - nLine + 1, - curField.c_str(), - strType.c_str() - ); + // we don't understand this field, but + // Netscape stores info in it, so don't warn + // about it + if ( curField.Left(16u) != "x-mozilla-flags=" ) + { + // don't flood the user with error + // messages if we don't understand + // something in his mailcap, but give + // them in debug mode because this might + // be useful for the programmer + wxLogDebug + ( + wxT("Mailcap file %s, line %d: " + "unknown field '%s' for the " + "MIME type '%s' ignored."), + strFileName.c_str(), + nLine + 1, + curField.c_str(), + strType.c_str() + ); + } } } @@ -1162,7 +2183,7 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) break; default: - wxFAIL_MSG("unknown field type in mailcap"); + wxFAIL_MSG(wxT("unknown field type in mailcap")); } // next token starts immediately after ';' @@ -1185,6 +2206,8 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) strPrintCmd, strTest); + // NB: because of complications below (we must get entries priority + // right), we can't use AddMailcapInfo() here, unfortunately. strType.MakeLower(); int nIndex = m_aTypes.Index(strType); if ( nIndex == wxNOT_FOUND ) { @@ -1192,30 +2215,55 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) m_aTypes.Add(strType); m_aEntries.Add(entry); - m_aExtensions.Add(""); + m_aExtensions.Add(wxT("")); m_aDescriptions.Add(strDesc); } else { - // modify the existing entry: the entry in one and the same file - // are read in top-to-bottom order, i.e. the entries read first - // should be tried before the entries below. However, the files - // read later should override the settings in the files read - // before, thus we Append() the new entry to the list if it has - // already occured in _this_ file, but Prepend() it if it - // occured in some of the previous ones. - if ( aEntryIndices.Index(nIndex) == wxNOT_FOUND ) { - // first time in this file - aEntryIndices.Add(nIndex); - entry->Prepend(m_aEntries[nIndex]); - m_aEntries[nIndex] = entry; + // modify the existing entry: the entries in one and the same + // file are read in top-to-bottom order, i.e. the entries read + // first should be tried before the entries below. However, + // the files read later should override the settings in the + // files read before (except if fallback is TRUE), thus we + // Insert() the new entry to the list if it has already + // occured in _this_ file, but Prepend() it if it occured in + // some of the previous ones and Append() to it in the + // fallback case + + if ( fallback ) { + // 'fallback' parameter prevents the entries from this + // file from overriding the other ones - always append + MailCapEntry *entryOld = m_aEntries[nIndex]; + if ( entryOld ) + entry->Append(entryOld); + else + m_aEntries[nIndex] = entry; } else { - // not the first time in _this_ file - entry->Append(m_aEntries[nIndex]); + int entryIndex = aEntryIndices.Index(nIndex); + if ( entryIndex == wxNOT_FOUND ) { + // first time in this file + aEntryIndices.Add(nIndex); + aLastIndices.Add(0); + + entry->Prepend(m_aEntries[nIndex]); + m_aEntries[nIndex] = entry; + } + else { + // not the first time in _this_ file + size_t nEntryIndex = (size_t)entryIndex; + MailCapEntry *entryOld = m_aEntries[nIndex]; + if ( entryOld ) + entry->Insert(entryOld, aLastIndices[nEntryIndex]); + else + m_aEntries[nIndex] = entry; + + // the indices were shifted by 1 + aLastIndices[nEntryIndex]++; + } } if ( !strDesc.IsEmpty() ) { - // @@ replace the old one - what else can we do?? + // replace the old one - what else can we do?? m_aDescriptions[nIndex] = strDesc; } } @@ -1226,9 +2274,34 @@ void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName) m_aTypes.Count() == m_aExtensions.Count() && m_aTypes.Count() == m_aDescriptions.Count() ); } + + return TRUE; } -#endif // OS type +size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes) +{ + mimetypes.Empty(); + + wxString type; + size_t count = m_aTypes.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + // don't return template types from here (i.e. anything containg '*') + type = m_aTypes[n]; + if ( type.Find(_T('*')) == wxNOT_FOUND ) + { + mimetypes.Add(type); + } + } + + return mimetypes.GetCount(); +} + +#endif + // OS type + +#endif + // wxUSE_FILE && wxUSE_TEXTFILE #endif // __WIN16__