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