1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/common/mimecmn.cpp
3// Purpose:     classes and functions to manage MIME types
4// Author:      Vadim Zeitlin
5// Modified by:
6//  Chris Elliott (biol75@york.ac.uk) 5 Dec 00: write support for Win32
7// Created:     23.09.98
8// RCS-ID:      $Id: mimecmn.cpp 47027 2007-06-29 18:23:39Z VZ $
9// Copyright:   (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
10// Licence:     wxWindows licence (part of wxExtra library)
11/////////////////////////////////////////////////////////////////////////////
12
13// ============================================================================
14// declarations
15// ============================================================================
16
17// ----------------------------------------------------------------------------
18// headers
19// ----------------------------------------------------------------------------
20
21// for compilers that support precompilation, includes "wx.h".
22#include "wx/wxprec.h"
23
24#ifdef __BORLANDC__
25    #pragma hdrstop
26#endif
27
28#if wxUSE_MIMETYPE
29
30#include "wx/mimetype.h"
31
32#ifndef WX_PRECOMP
33    #include "wx/dynarray.h"
34    #include "wx/string.h"
35    #include "wx/intl.h"
36    #include "wx/log.h"
37    #include "wx/module.h"
38#endif //WX_PRECOMP
39
40#include "wx/file.h"
41#include "wx/iconloc.h"
42#include "wx/confbase.h"
43
44// other standard headers
45#include <ctype.h>
46
47// implementation classes:
48#if defined(__WXMSW__)
49    #include "wx/msw/mimetype.h"
50#elif defined(__WXMAC__)
51    #include "wx/mac/mimetype.h"
52#elif defined(__WXPM__) || defined (__EMX__)
53    #include "wx/os2/mimetype.h"
54    #undef __UNIX__
55#elif defined(__DOS__)
56    #include "wx/msdos/mimetype.h"
57#else // Unix
58    #include "wx/unix/mimetype.h"
59#endif
60
61// ============================================================================
62// common classes
63// ============================================================================
64
65// ----------------------------------------------------------------------------
66// wxMimeTypeCommands
67// ----------------------------------------------------------------------------
68
69void
70wxMimeTypeCommands::AddOrReplaceVerb(const wxString& verb, const wxString& cmd)
71{
72    int n = m_verbs.Index(verb, false /* ignore case */);
73    if ( n == wxNOT_FOUND )
74    {
75        m_verbs.Add(verb);
76        m_commands.Add(cmd);
77    }
78    else
79    {
80        m_commands[n] = cmd;
81    }
82}
83
84wxString
85wxMimeTypeCommands::GetCommandForVerb(const wxString& verb, size_t *idx) const
86{
87    wxString s;
88
89    int n = m_verbs.Index(verb);
90    if ( n != wxNOT_FOUND )
91    {
92        s = m_commands[(size_t)n];
93        if ( idx )
94            *idx = n;
95    }
96    else if ( idx )
97    {
98        // different from any valid index
99        *idx = (size_t)-1;
100    }
101
102    return s;
103}
104
105wxString wxMimeTypeCommands::GetVerbCmd(size_t n) const
106{
107    return m_verbs[n] + wxT('=') + m_commands[n];
108}
109
110// ----------------------------------------------------------------------------
111// wxFileTypeInfo
112// ----------------------------------------------------------------------------
113
114wxFileTypeInfo::wxFileTypeInfo(const wxChar *mimeType,
115                               const wxChar *openCmd,
116                               const wxChar *printCmd,
117                               const wxChar *desc,
118                               ...)
119              : m_mimeType(mimeType),
120                m_openCmd(openCmd),
121                m_printCmd(printCmd),
122                m_desc(desc)
123{
124    va_list argptr;
125    va_start(argptr, desc);
126
127    for ( ;; )
128    {
129        // icc gives this warning in its own va_arg() macro, argh
130#ifdef __INTELC__
131    #pragma warning(push)
132    #pragma warning(disable: 1684)
133#endif
134
135        const wxChar *ext = va_arg(argptr, const wxChar *);
136
137#ifdef __INTELC__
138    #pragma warning(pop)
139#endif
140        if ( !ext )
141        {
142            // NULL terminates the list
143            break;
144        }
145
146        m_exts.Add(ext);
147    }
148
149    va_end(argptr);
150}
151
152
153wxFileTypeInfo::wxFileTypeInfo(const wxArrayString& sArray)
154{
155    m_mimeType = sArray [0u];
156    m_openCmd  = sArray [1u];
157    m_printCmd = sArray [2u];
158    m_desc     = sArray [3u];
159
160    size_t count = sArray.GetCount();
161    for ( size_t i = 4; i < count; i++ )
162    {
163        m_exts.Add(sArray[i]);
164    }
165}
166
167#include "wx/arrimpl.cpp"
168WX_DEFINE_OBJARRAY(wxArrayFileTypeInfo)
169
170// ============================================================================
171// implementation of the wrapper classes
172// ============================================================================
173
174// ----------------------------------------------------------------------------
175// wxFileType
176// ----------------------------------------------------------------------------
177
178/* static */
179wxString wxFileType::ExpandCommand(const wxString& command,
180                                   const wxFileType::MessageParameters& params)
181{
182    bool hasFilename = false;
183
184    wxString str;
185    for ( const wxChar *pc = command.c_str(); *pc != wxT('\0'); pc++ ) {
186        if ( *pc == wxT('%') ) {
187            switch ( *++pc ) {
188                case wxT('s'):
189                    // '%s' expands into file name (quoted because it might
190                    // contain spaces) - except if there are already quotes
191                    // there because otherwise some programs may get confused
192                    // by double double quotes
193#if 0
194                    if ( *(pc - 2) == wxT('"') )
195                        str << params.GetFileName();
196                    else
197                        str << wxT('"') << params.GetFileName() << wxT('"');
198#endif
199                    str << params.GetFileName();
200                    hasFilename = true;
201                    break;
202
203                case wxT('t'):
204                    // '%t' expands into MIME type (quote it too just to be
205                    // consistent)
206                    str << wxT('\'') << params.GetMimeType() << wxT('\'');
207                    break;
208
209                case wxT('{'):
210                    {
211                        const wxChar *pEnd = wxStrchr(pc, wxT('}'));
212                        if ( pEnd == NULL ) {
213                            wxString mimetype;
214                            wxLogWarning(_("Unmatched '{' in an entry for mime type %s."),
215                                         params.GetMimeType().c_str());
216                            str << wxT("%{");
217                        }
218                        else {
219                            wxString param(pc + 1, pEnd - pc - 1);
220                            str << wxT('\'') << params.GetParamValue(param) << wxT('\'');
221                            pc = pEnd;
222                        }
223                    }
224                    break;
225
226                case wxT('n'):
227                case wxT('F'):
228                    // TODO %n is the number of parts, %F is an array containing
229                    //      the names of temp files these parts were written to
230                    //      and their mime types.
231                    break;
232
233                default:
234                    wxLogDebug(wxT("Unknown field %%%c in command '%s'."),
235                               *pc, command.c_str());
236                    str << *pc;
237            }
238        }
239        else {
240            str << *pc;
241        }
242    }
243
244    // metamail(1) man page states that if the mailcap entry doesn't have '%s'
245    // the program will accept the data on stdin so normally we should append
246    // "< %s" to the end of the command in such case, but not all commands
247    // behave like this, in particular a common test is 'test -n "$DISPLAY"'
248    // and appending "< %s" to this command makes the test fail... I don't
249    // know of the correct solution, try to guess what we have to do.
250
251    // test now carried out on reading file so test should never get here
252    if ( !hasFilename && !str.empty()
253#ifdef __UNIX__
254                      && !str.StartsWith(_T("test "))
255#endif // Unix
256       ) {
257        str << wxT(" < '") << params.GetFileName() << wxT('\'');
258    }
259
260    return str;
261}
262
263wxFileType::wxFileType(const wxFileTypeInfo& info)
264{
265    m_info = &info;
266    m_impl = NULL;
267}
268
269wxFileType::wxFileType()
270{
271    m_info = NULL;
272    m_impl = new wxFileTypeImpl;
273}
274
275wxFileType::~wxFileType()
276{
277    if ( m_impl )
278        delete m_impl;
279}
280
281bool wxFileType::GetExtensions(wxArrayString& extensions)
282{
283    if ( m_info )
284    {
285        extensions = m_info->GetExtensions();
286        return true;
287    }
288
289    return m_impl->GetExtensions(extensions);
290}
291
292bool wxFileType::GetMimeType(wxString *mimeType) const
293{
294    wxCHECK_MSG( mimeType, false, _T("invalid parameter in GetMimeType") );
295
296    if ( m_info )
297    {
298        *mimeType = m_info->GetMimeType();
299
300        return true;
301    }
302
303    return m_impl->GetMimeType(mimeType);
304}
305
306bool wxFileType::GetMimeTypes(wxArrayString& mimeTypes) const
307{
308    if ( m_info )
309    {
310        mimeTypes.Clear();
311        mimeTypes.Add(m_info->GetMimeType());
312
313        return true;
314    }
315
316    return m_impl->GetMimeTypes(mimeTypes);
317}
318
319bool wxFileType::GetIcon(wxIconLocation *iconLoc) const
320{
321    if ( m_info )
322    {
323        if ( iconLoc )
324        {
325            iconLoc->SetFileName(m_info->GetIconFile());
326#ifdef __WXMSW__
327            iconLoc->SetIndex(m_info->GetIconIndex());
328#endif // __WXMSW__
329        }
330
331        return true;
332    }
333
334    return m_impl->GetIcon(iconLoc);
335}
336
337bool
338wxFileType::GetIcon(wxIconLocation *iconloc,
339                    const MessageParameters& params) const
340{
341    if ( !GetIcon(iconloc) )
342    {
343        return false;
344    }
345
346    // we may have "%s" in the icon location string, at least under Windows, so
347    // expand this
348    if ( iconloc )
349    {
350        iconloc->SetFileName(ExpandCommand(iconloc->GetFileName(), params));
351    }
352
353    return true;
354}
355
356bool wxFileType::GetDescription(wxString *desc) const
357{
358    wxCHECK_MSG( desc, false, _T("invalid parameter in GetDescription") );
359
360    if ( m_info )
361    {
362        *desc = m_info->GetDescription();
363
364        return true;
365    }
366
367    return m_impl->GetDescription(desc);
368}
369
370bool
371wxFileType::GetOpenCommand(wxString *openCmd,
372                           const wxFileType::MessageParameters& params) const
373{
374    wxCHECK_MSG( openCmd, false, _T("invalid parameter in GetOpenCommand") );
375
376    if ( m_info )
377    {
378        *openCmd = ExpandCommand(m_info->GetOpenCommand(), params);
379
380        return true;
381    }
382
383    return m_impl->GetOpenCommand(openCmd, params);
384}
385
386wxString wxFileType::GetOpenCommand(const wxString& filename) const
387{
388    wxString cmd;
389    if ( !GetOpenCommand(&cmd, filename) )
390    {
391        // return empty string to indicate an error
392        cmd.clear();
393    }
394
395    return cmd;
396}
397
398bool
399wxFileType::GetPrintCommand(wxString *printCmd,
400                            const wxFileType::MessageParameters& params) const
401{
402    wxCHECK_MSG( printCmd, false, _T("invalid parameter in GetPrintCommand") );
403
404    if ( m_info )
405    {
406        *printCmd = ExpandCommand(m_info->GetPrintCommand(), params);
407
408        return true;
409    }
410
411    return m_impl->GetPrintCommand(printCmd, params);
412}
413
414
415size_t wxFileType::GetAllCommands(wxArrayString *verbs,
416                                  wxArrayString *commands,
417                                  const wxFileType::MessageParameters& params) const
418{
419    if ( verbs )
420        verbs->Clear();
421    if ( commands )
422        commands->Clear();
423
424#if defined (__WXMSW__)  || defined(__UNIX__)
425    return m_impl->GetAllCommands(verbs, commands, params);
426#else // !__WXMSW__ || Unix
427    // we don't know how to retrieve all commands, so just try the 2 we know
428    // about
429    size_t count = 0;
430    wxString cmd;
431    if ( GetOpenCommand(&cmd, params) )
432    {
433        if ( verbs )
434            verbs->Add(_T("Open"));
435        if ( commands )
436            commands->Add(cmd);
437        count++;
438    }
439
440    if ( GetPrintCommand(&cmd, params) )
441    {
442        if ( verbs )
443            verbs->Add(_T("Print"));
444        if ( commands )
445            commands->Add(cmd);
446
447        count++;
448    }
449
450    return count;
451#endif // __WXMSW__/| __UNIX__
452}
453
454bool wxFileType::Unassociate()
455{
456#if defined(__WXMSW__)
457    return m_impl->Unassociate();
458#elif defined(__UNIX__)
459    return m_impl->Unassociate(this);
460#else
461    wxFAIL_MSG( _T("not implemented") ); // TODO
462    return false;
463#endif
464}
465
466bool wxFileType::SetCommand(const wxString& cmd,
467                            const wxString& verb,
468                            bool overwriteprompt)
469{
470#if defined (__WXMSW__)  || defined(__UNIX__)
471    return m_impl->SetCommand(cmd, verb, overwriteprompt);
472#else
473    wxUnusedVar(cmd);
474    wxUnusedVar(verb);
475    wxUnusedVar(overwriteprompt);
476    wxFAIL_MSG(_T("not implemented"));
477    return false;
478#endif
479}
480
481bool wxFileType::SetDefaultIcon(const wxString& cmd, int index)
482{
483    wxString sTmp = cmd;
484#ifdef __WXMSW__
485    // VZ: should we do this?
486    // chris elliott : only makes sense in MS windows
487    if ( sTmp.empty() )
488        GetOpenCommand(&sTmp, wxFileType::MessageParameters(wxEmptyString, wxEmptyString));
489#endif
490    wxCHECK_MSG( !sTmp.empty(), false, _T("need the icon file") );
491
492#if defined (__WXMSW__) || defined(__UNIX__)
493    return m_impl->SetDefaultIcon (cmd, index);
494#else
495    wxUnusedVar(index);
496    wxFAIL_MSG(_T("not implemented"));
497    return false;
498#endif
499}
500
501// ----------------------------------------------------------------------------
502// wxMimeTypesManagerFactory
503// ----------------------------------------------------------------------------
504
505wxMimeTypesManagerFactory *wxMimeTypesManagerFactory::m_factory = NULL;
506
507/* static */
508void wxMimeTypesManagerFactory::Set(wxMimeTypesManagerFactory *factory)
509{
510    delete m_factory;
511
512    m_factory = factory;
513}
514
515/* static */
516wxMimeTypesManagerFactory *wxMimeTypesManagerFactory::Get()
517{
518    if ( !m_factory )
519        m_factory = new wxMimeTypesManagerFactory;
520
521    return m_factory;
522}
523
524wxMimeTypesManagerImpl *wxMimeTypesManagerFactory::CreateMimeTypesManagerImpl()
525{
526    return new wxMimeTypesManagerImpl;
527}
528
529// ----------------------------------------------------------------------------
530// wxMimeTypesManager
531// ----------------------------------------------------------------------------
532
533void wxMimeTypesManager::EnsureImpl()
534{
535    if ( !m_impl )
536        m_impl = wxMimeTypesManagerFactory::Get()->CreateMimeTypesManagerImpl();
537}
538
539bool wxMimeTypesManager::IsOfType(const wxString& mimeType,
540                                  const wxString& wildcard)
541{
542    wxASSERT_MSG( mimeType.Find(wxT('*')) == wxNOT_FOUND,
543                  wxT("first MIME type can't contain wildcards") );
544
545    // all comparaisons are case insensitive (2nd arg of IsSameAs() is false)
546    if ( wildcard.BeforeFirst(wxT('/')).
547            IsSameAs(mimeType.BeforeFirst(wxT('/')), false) )
548    {
549        wxString strSubtype = wildcard.AfterFirst(wxT('/'));
550
551        if ( strSubtype == wxT("*") ||
552             strSubtype.IsSameAs(mimeType.AfterFirst(wxT('/')), false) )
553        {
554            // matches (either exactly or it's a wildcard)
555            return true;
556        }
557    }
558
559    return false;
560}
561
562wxMimeTypesManager::wxMimeTypesManager()
563{
564    m_impl = NULL;
565}
566
567wxMimeTypesManager::~wxMimeTypesManager()
568{
569    if ( m_impl )
570        delete m_impl;
571}
572
573bool wxMimeTypesManager::Unassociate(wxFileType *ft)
574{
575    EnsureImpl();
576
577#if defined(__UNIX__) && !defined(__CYGWIN__) && !defined(__WINE__)
578    return m_impl->Unassociate(ft);
579#else
580    return ft->Unassociate();
581#endif
582}
583
584
585wxFileType *
586wxMimeTypesManager::Associate(const wxFileTypeInfo& ftInfo)
587{
588    EnsureImpl();
589
590#if defined(__WXMSW__) || defined(__UNIX__)
591    return m_impl->Associate(ftInfo);
592#else // other platforms
593    wxUnusedVar(ftInfo);
594    wxFAIL_MSG( _T("not implemented") ); // TODO
595    return NULL;
596#endif // platforms
597}
598
599wxFileType *
600wxMimeTypesManager::GetFileTypeFromExtension(const wxString& ext)
601{
602    EnsureImpl();
603
604    wxString::const_iterator i = ext.begin();
605    const wxString::const_iterator end = ext.end();
606    wxString extWithoutDot;
607    if ( i != end && *i == '.' )
608        extWithoutDot.assign(++i, ext.end());
609    else
610        extWithoutDot = ext;
611
612    wxCHECK_MSG( !ext.empty(), NULL, _T("extension can't be empty") );
613
614    wxFileType *ft = m_impl->GetFileTypeFromExtension(extWithoutDot);
615
616    if ( !ft ) {
617        // check the fallbacks
618        //
619        // TODO linear search is potentially slow, perhaps we should use a
620        //       sorted array?
621        size_t count = m_fallbacks.GetCount();
622        for ( size_t n = 0; n < count; n++ ) {
623            if ( m_fallbacks[n].GetExtensions().Index(ext) != wxNOT_FOUND ) {
624                ft = new wxFileType(m_fallbacks[n]);
625
626                break;
627            }
628        }
629    }
630
631    return ft;
632}
633
634wxFileType *
635wxMimeTypesManager::GetFileTypeFromMimeType(const wxString& mimeType)
636{
637    EnsureImpl();
638    wxFileType *ft = m_impl->GetFileTypeFromMimeType(mimeType);
639
640    if ( !ft ) {
641        // check the fallbacks
642        //
643        // TODO linear search is potentially slow, perhaps we should use a
644        //      sorted array?
645        size_t count = m_fallbacks.GetCount();
646        for ( size_t n = 0; n < count; n++ ) {
647            if ( wxMimeTypesManager::IsOfType(mimeType,
648                                              m_fallbacks[n].GetMimeType()) ) {
649                ft = new wxFileType(m_fallbacks[n]);
650
651                break;
652            }
653        }
654    }
655
656    return ft;
657}
658
659bool wxMimeTypesManager::ReadMailcap(const wxString& filename, bool fallback)
660{
661    EnsureImpl();
662    return m_impl->ReadMailcap(filename, fallback);
663}
664
665bool wxMimeTypesManager::ReadMimeTypes(const wxString& filename)
666{
667    EnsureImpl();
668    return m_impl->ReadMimeTypes(filename);
669}
670
671void wxMimeTypesManager::AddFallbacks(const wxFileTypeInfo *filetypes)
672{
673    EnsureImpl();
674    for ( const wxFileTypeInfo *ft = filetypes; ft && ft->IsValid(); ft++ ) {
675        AddFallback(*ft);
676    }
677}
678
679size_t wxMimeTypesManager::EnumAllFileTypes(wxArrayString& mimetypes)
680{
681    EnsureImpl();
682    size_t countAll = m_impl->EnumAllFileTypes(mimetypes);
683
684    // add the fallback filetypes
685    size_t count = m_fallbacks.GetCount();
686    for ( size_t n = 0; n < count; n++ ) {
687        if ( mimetypes.Index(m_fallbacks[n].GetMimeType()) == wxNOT_FOUND ) {
688            mimetypes.Add(m_fallbacks[n].GetMimeType());
689            countAll++;
690        }
691    }
692
693    return countAll;
694}
695
696void wxMimeTypesManager::Initialize(int mcapStyle,
697                                    const wxString& sExtraDir)
698{
699#if defined(__UNIX__) && !defined(__CYGWIN__) && !defined(__WINE__)
700    EnsureImpl();
701
702    m_impl->Initialize(mcapStyle, sExtraDir);
703#else
704    (void)mcapStyle;
705    (void)sExtraDir;
706#endif // Unix
707}
708
709// and this function clears all the data from the manager
710void wxMimeTypesManager::ClearData()
711{
712#if defined(__UNIX__) && !defined(__CYGWIN__) && !defined(__WINE__)
713    EnsureImpl();
714
715    m_impl->ClearData();
716#endif // Unix
717}
718
719// ----------------------------------------------------------------------------
720// global data and wxMimeTypeCmnModule
721// ----------------------------------------------------------------------------
722
723// private object
724static wxMimeTypesManager gs_mimeTypesManager;
725
726// and public pointer
727wxMimeTypesManager *wxTheMimeTypesManager = &gs_mimeTypesManager;
728
729class wxMimeTypeCmnModule: public wxModule
730{
731public:
732    wxMimeTypeCmnModule() : wxModule() { }
733
734    virtual bool OnInit() { return true; }
735    virtual void OnExit()
736    {
737        wxMimeTypesManagerFactory::Set(NULL);
738
739        if ( gs_mimeTypesManager.m_impl != NULL )
740        {
741            delete gs_mimeTypesManager.m_impl;
742            gs_mimeTypesManager.m_impl = NULL;
743            gs_mimeTypesManager.m_fallbacks.Clear();
744        }
745    }
746
747    DECLARE_DYNAMIC_CLASS(wxMimeTypeCmnModule)
748};
749
750IMPLEMENT_DYNAMIC_CLASS(wxMimeTypeCmnModule, wxModule)
751
752#endif // wxUSE_MIMETYPE
753