]> git.saurik.com Git - wxWidgets.git/blob - src/msw/mimetype.cpp
Changed code to use the RM environment variable (if one exists) to delete files....
[wxWidgets.git] / src / msw / mimetype.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: msw/mimetype.cpp
3 // Purpose: classes and functions to manage MIME types
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 23.09.98
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
11
12 #ifdef __GNUG__
13 #pragma implementation "mimetype.h"
14 #endif
15
16 // for compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 // Doesn't compile in WIN16 mode
24 #ifndef __WIN16__
25
26 #ifndef WX_PRECOMP
27 #include "wx/string.h"
28 #if wxUSE_GUI
29 #include "wx/icon.h"
30 #include "wx/msgdlg.h"
31 #endif
32 #endif //WX_PRECOMP
33
34 #include "wx/log.h"
35 #include "wx/file.h"
36 #include "wx/intl.h"
37 #include "wx/dynarray.h"
38 #include "wx/confbase.h"
39
40 #ifdef __WXMSW__
41 #include "wx/msw/registry.h"
42 #include "windows.h"
43 #endif // OS
44
45 #include "wx/msw/mimetype.h"
46
47 // other standard headers
48 #include <ctype.h>
49
50 // in case we're compiling in non-GUI mode
51 class WXDLLEXPORT wxIcon;
52
53 // These classes use Windows registry to retrieve the required information.
54 //
55 // Keys used (not all of them are documented, so it might actually stop working
56 // in future versions of Windows...):
57 // 1. "HKCR\MIME\Database\Content Type" contains subkeys for all known MIME
58 // types, each key has a string value "Extension" which gives (dot preceded)
59 // extension for the files of this MIME type.
60 //
61 // 2. "HKCR\.ext" contains
62 // a) unnamed value containing the "filetype"
63 // b) value "Content Type" containing the MIME type
64 //
65 // 3. "HKCR\filetype" contains
66 // a) unnamed value containing the description
67 // b) subkey "DefaultIcon" with single unnamed value giving the icon index in
68 // an icon file
69 // c) shell\open\command and shell\open\print subkeys containing the commands
70 // to open/print the file (the positional parameters are introduced by %1,
71 // %2, ... in these strings, we change them to %s ourselves)
72
73 // although I don't know of any official documentation which mentions this
74 // location, uses it, so it isn't likely to change
75 static const wxChar *MIME_DATABASE_KEY = wxT("MIME\\Database\\Content Type\\");
76
77 void wxFileTypeImpl::Init(const wxString& strFileType, const wxString& ext)
78 {
79 // VZ: does it? (FIXME)
80 wxCHECK_RET( !ext.IsEmpty(), _T("needs an extension") );
81
82 if ( ext[0u] != wxT('.') ) {
83 m_ext = wxT('.');
84 }
85 m_ext << ext;
86
87 m_strFileType = strFileType;
88 if ( !strFileType ) {
89 m_strFileType = m_ext.AfterFirst('.') + "_auto_file";
90 }
91 }
92
93 wxString wxFileTypeImpl::GetVerbPath(const wxString& verb) const
94 {
95 wxString path;
96 path << m_strFileType << _T("\\shell\\") << verb << _T("\\command");
97 return path;
98 }
99
100 size_t wxFileTypeImpl::GetAllCommands(wxArrayString *verbs,
101 wxArrayString *commands,
102 const wxFileType::MessageParameters& params) const
103 {
104 wxCHECK_MSG( !m_ext.IsEmpty(), 0, _T("GetAllCommands() needs an extension") );
105
106 if ( m_strFileType.IsEmpty() )
107 {
108 // get it from the registry
109 wxFileTypeImpl *self = wxConstCast(this, wxFileTypeImpl);
110 wxRegKey rkey(wxRegKey::HKCR, m_ext);
111 if ( !rkey.Exists() || !rkey.QueryValue(_T(""), self->m_strFileType) )
112 {
113 wxLogDebug(_T("Can't get the filetype for extension '%s'."),
114 m_ext.c_str());
115
116 return 0;
117 }
118 }
119
120 // enum all subkeys of HKCR\filetype\shell
121 size_t count = 0;
122 wxRegKey rkey(wxRegKey::HKCR, m_strFileType + _T("\\shell"));
123 long dummy;
124 wxString verb;
125 bool ok = rkey.GetFirstKey(verb, dummy);
126 while ( ok )
127 {
128 wxString command = wxFileType::ExpandCommand(GetCommand(verb), params);
129
130 // we want the open bverb to eb always the first
131
132 if ( verb.CmpNoCase(_T("open")) == 0 )
133 {
134 if ( verbs )
135 verbs->Insert(verb, 0);
136 if ( commands )
137 commands->Insert(command, 0);
138 }
139 else // anything else than "open"
140 {
141 if ( verbs )
142 verbs->Add(verb);
143 if ( commands )
144 commands->Add(command);
145 }
146
147 count++;
148
149 ok = rkey.GetNextKey(verb, dummy);
150 }
151
152 return count;
153 }
154
155 // ----------------------------------------------------------------------------
156 // modify the registry database
157 // ----------------------------------------------------------------------------
158
159 bool wxFileTypeImpl::EnsureExtKeyExists()
160 {
161 wxRegKey rkey(wxRegKey::HKCR, m_ext);
162 if ( !rkey.Exists() )
163 {
164 if ( !rkey.Create() || !rkey.SetValue(_T(""), m_strFileType) )
165 {
166 wxLogError(_("Failed to create registry entry for '%s' files."),
167 m_ext.c_str());
168 return FALSE;
169 }
170 }
171
172 return TRUE;
173 }
174
175 bool wxFileTypeImpl::SetCommand(const wxString& cmd,
176 const wxString& verb,
177 bool overwriteprompt)
178 {
179 wxCHECK_MSG( !m_ext.IsEmpty() && !verb.IsEmpty(), FALSE,
180 _T("SetCommand() needs an extension and a verb") );
181
182 if ( !EnsureExtKeyExists() )
183 return FALSE;
184
185 wxRegKey rkey(wxRegKey::HKCR, GetVerbPath(verb));
186
187 if ( rkey.Exists() && overwriteprompt )
188 {
189 #if wxUSE_GUI
190 wxString old;
191 rkey.QueryValue(wxT(""), old);
192 if ( wxMessageBox
193 (
194 wxString::Format(
195 _("Do you want to overwrite the command used to %s "
196 "files with extension \"%s\" (current value is '%s', "
197 "new value is '%s')?"),
198 verb.c_str(),
199 m_ext.c_str(),
200 old.c_str(),
201 cmd.c_str()),
202 _("Confirm registry update"),
203 wxYES_NO | wxICON_QUESTION
204 ) != wxYES )
205 #endif // wxUSE_GUI
206 {
207 // cancelled by user
208 return FALSE;
209 }
210 }
211
212 // TODO:
213 // 1. translate '%s' to '%1' instead of always adding it
214 // 2. create DDEExec value if needed (undo GetCommand)
215 return rkey.Create() && rkey.SetValue(_T(""), cmd + _T(" \"%1\"") );
216 }
217
218 bool wxFileTypeImpl::SetMimeType(const wxString& mimeTypeOrig)
219 {
220 wxCHECK_MSG( !m_ext.IsEmpty(), FALSE, _T("SetMimeType() needs extension") );
221
222 if ( !EnsureExtKeyExists() )
223 return FALSE;
224
225 // VZ: is this really useful? (FIXME)
226 wxString mimeType;
227 if ( !mimeTypeOrig )
228 {
229 // make up a default value for it
230 wxString cmd;
231 wxSplitPath(GetCommand(_T("open")), NULL, &cmd, NULL);
232 mimeType << _T("application/x-") << cmd;
233 }
234 else
235 {
236 mimeType = mimeTypeOrig;
237 }
238
239 wxRegKey rkey(wxRegKey::HKCR, m_ext);
240 return rkey.Create() && rkey.SetValue(_T("Content Type"), mimeType);
241 }
242
243 bool wxFileTypeImpl::SetDefaultIcon(const wxString& cmd, int index)
244 {
245 wxCHECK_MSG( !m_ext.IsEmpty(), FALSE, _T("SetMimeType() needs extension") );
246 wxCHECK_MSG( wxFileExists(cmd), FALSE, _T("Icon file not found.") );
247
248 if ( !EnsureExtKeyExists() )
249 return FALSE;
250
251 wxRegKey rkey(wxRegKey::HKCR, m_strFileType + _T("\\DefaultIcon"));
252
253 return rkey.Create() &&
254 rkey.SetValue(_T(""),
255 wxString::Format(_T("%s,%d"), cmd.c_str(), index));
256 }
257
258 // ----------------------------------------------------------------------------
259 // remove file association
260 // ----------------------------------------------------------------------------
261
262 bool wxFileTypeImpl::RemoveCommand(const wxString& verb)
263 {
264 wxCHECK_MSG( !m_ext.IsEmpty() && !verb.IsEmpty(), FALSE,
265 _T("RemoveCommand() needs an extension and a verb") );
266
267 wxString sKey = m_strFileType;
268 wxRegKey rkey(wxRegKey::HKCR, GetVerbPath(verb));
269
270 // if the key already doesn't exist, it's a success
271 return !rkey.Exists() || rkey.DeleteSelf();
272 }
273
274 bool wxFileTypeImpl::RemoveMimeType()
275 {
276 wxCHECK_MSG( !m_ext.IsEmpty(), FALSE, _T("RemoveMimeType() needs extension") );
277
278 wxRegKey rkey(wxRegKey::HKCR, m_ext);
279 return !rkey.Exists() || rkey.DeleteSelf();
280 }
281
282 bool wxFileTypeImpl::RemoveDefaultIcon()
283 {
284 wxCHECK_MSG( !m_ext.IsEmpty(), FALSE,
285 _T("RemoveDefaultIcon() needs extension") );
286
287 wxRegKey rkey (wxRegKey::HKCR, m_strFileType + _T("\\DefaultIcon"));
288 return !rkey.Exists() || rkey.DeleteSelf();
289 }
290
291 wxString wxFileTypeImpl::GetCommand(const wxChar *verb) const
292 {
293 // suppress possible error messages
294 wxLogNull nolog;
295 wxString strKey;
296
297 if ( wxRegKey(wxRegKey::HKCR, m_ext + _T("\\shell")).Exists() )
298 strKey = m_ext;
299 if ( wxRegKey(wxRegKey::HKCR, m_strFileType + _T("\\shell")).Exists() )
300 strKey = m_strFileType;
301
302 if ( !strKey )
303 {
304 // no info
305 return wxEmptyString;
306 }
307
308 strKey << wxT("\\shell\\") << verb;
309 wxRegKey key(wxRegKey::HKCR, strKey + _T("\\command"));
310 wxString command;
311 if ( key.Open() ) {
312 // it's the default value of the key
313 if ( key.QueryValue(wxT(""), command) ) {
314 // transform it from '%1' to '%s' style format string (now also
315 // test for %L - apparently MS started using it as well for the
316 // same purpose)
317
318 // NB: we don't make any attempt to verify that the string is valid,
319 // i.e. doesn't contain %2, or second %1 or .... But we do make
320 // sure that we return a string with _exactly_ one '%s'!
321 bool foundFilename = FALSE;
322 size_t len = command.Len();
323 for ( size_t n = 0; (n < len) && !foundFilename; n++ ) {
324 if ( command[n] == wxT('%') &&
325 (n + 1 < len) &&
326 (command[n + 1] == wxT('1') ||
327 command[n + 1] == wxT('L')) ) {
328 // replace it with '%s'
329 command[n + 1] = wxT('s');
330
331 foundFilename = TRUE;
332 }
333 }
334
335 #if wxUSE_IPC
336 // look whether we must issue some DDE requests to the application
337 // (and not just launch it)
338 strKey += _T("\\DDEExec");
339 wxRegKey keyDDE(wxRegKey::HKCR, strKey);
340 if ( keyDDE.Open() ) {
341 wxString ddeCommand, ddeServer, ddeTopic;
342 keyDDE.QueryValue(_T(""), ddeCommand);
343 ddeCommand.Replace(_T("%1"), _T("%s"));
344
345 wxRegKey(wxRegKey::HKCR, strKey + _T("\\Application")).
346 QueryValue(_T(""), ddeServer);
347 wxRegKey(wxRegKey::HKCR, strKey + _T("\\Topic")).
348 QueryValue(_T(""), ddeTopic);
349
350 // HACK: we use a special feature of wxExecute which exists
351 // just because we need it here: it will establish DDE
352 // conversation with the program it just launched
353 command.Prepend(_T("WX_DDE#"));
354 command << _T('#') << ddeServer
355 << _T('#') << ddeTopic
356 << _T('#') << ddeCommand;
357 }
358 else
359 #endif // wxUSE_IPC
360 if ( !foundFilename ) {
361 // we didn't find any '%1' - the application doesn't know which
362 // file to open (note that we only do it if there is no DDEExec
363 // subkey)
364 //
365 // HACK: append the filename at the end, hope that it will do
366 command << wxT(" %s");
367 }
368 }
369 }
370 //else: no such file type or no value, will return empty string
371
372 return command;
373 }
374
375 bool
376 wxFileTypeImpl::GetOpenCommand(wxString *openCmd,
377 const wxFileType::MessageParameters& params)
378 const
379 {
380 wxString cmd;
381 if ( m_info ) {
382 cmd = m_info->GetOpenCommand();
383 }
384 else {
385 cmd = GetCommand(wxT("open"));
386 }
387
388 *openCmd = wxFileType::ExpandCommand(cmd, params);
389
390 return !openCmd->IsEmpty();
391 }
392
393 bool
394 wxFileTypeImpl::GetPrintCommand(wxString *printCmd,
395 const wxFileType::MessageParameters& params)
396 const
397 {
398 wxString cmd;
399 if ( m_info ) {
400 cmd = m_info->GetPrintCommand();
401 }
402 else {
403 cmd = GetCommand(wxT("print"));
404 }
405
406 *printCmd = wxFileType::ExpandCommand(cmd, params);
407
408 return !printCmd->IsEmpty();
409 }
410
411 // TODO this function is half implemented
412 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
413 {
414 if ( m_info ) {
415 extensions = m_info->GetExtensions();
416
417 return TRUE;
418 }
419 else if ( m_ext.IsEmpty() ) {
420 // the only way to get the list of extensions from the file type is to
421 // scan through all extensions in the registry - too slow...
422 return FALSE;
423 }
424 else {
425 extensions.Empty();
426 extensions.Add(m_ext);
427
428 // it's a lie too, we don't return _all_ extensions...
429 return TRUE;
430 }
431 }
432
433 bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const
434 {
435 if ( m_info ) {
436 // we already have it
437 *mimeType = m_info->GetMimeType();
438
439 return TRUE;
440 }
441
442 // suppress possible error messages
443 wxLogNull nolog;
444 wxRegKey key(wxRegKey::HKCR, m_ext);
445
446 return key.Open() && key.QueryValue(wxT("Content Type"), *mimeType);
447 }
448
449 bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
450 {
451 wxString s;
452
453 if ( !GetMimeType(&s) )
454 {
455 return FALSE;
456 }
457
458 mimeTypes.Clear();
459 mimeTypes.Add(s);
460 return TRUE;
461 }
462
463
464 bool wxFileTypeImpl::GetIcon(wxIcon *icon,
465 wxString *iconFile,
466 int *iconIndex) const
467 {
468 #if wxUSE_GUI
469 if ( m_info ) {
470 // we don't have icons in the fallback resources
471 return FALSE;
472 }
473
474 wxString strIconKey;
475 strIconKey << m_strFileType << wxT("\\DefaultIcon");
476
477 // suppress possible error messages
478 wxLogNull nolog;
479 wxRegKey key(wxRegKey::HKCR, strIconKey);
480
481 if ( key.Open() ) {
482 wxString strIcon;
483 // it's the default value of the key
484 if ( key.QueryValue(wxT(""), strIcon) ) {
485 // the format is the following: <full path to file>, <icon index>
486 // NB: icon index may be negative as well as positive and the full
487 // path may contain the environment variables inside '%'
488 wxString strFullPath = strIcon.BeforeLast(wxT(',')),
489 strIndex = strIcon.AfterLast(wxT(','));
490
491 // index may be omitted, in which case BeforeLast(',') is empty and
492 // AfterLast(',') is the whole string
493 if ( strFullPath.IsEmpty() ) {
494 strFullPath = strIndex;
495 strIndex = wxT("0");
496 }
497
498 wxString strExpPath = wxExpandEnvVars(strFullPath);
499 int nIndex = wxAtoi(strIndex) - 1 ; //bug here we need C based counting!!
500
501 HICON hIcon = ExtractIcon(GetModuleHandle(NULL), strExpPath, nIndex);
502 switch ( (int)hIcon ) {
503 case 0: // means no icons were found
504 case 1: // means no such file or it wasn't a DLL/EXE/OCX/ICO/...
505 wxLogDebug(wxT("incorrect registry entry '%s': no such icon."),
506 key.GetName().c_str());
507 break;
508
509 default:
510 icon->SetHICON((WXHICON)hIcon);
511 if ( iconIndex )
512 *iconIndex = nIndex;
513 if ( iconFile )
514 *iconFile = strFullPath;
515 return TRUE;
516 }
517 }
518 }
519
520 // no such file type or no value or incorrect icon entry
521 #endif // wxUSE_GUI
522
523 return FALSE;
524 }
525
526 bool wxFileTypeImpl::GetDescription(wxString *desc) const
527 {
528 if ( m_info ) {
529 // we already have it
530 *desc = m_info->GetDescription();
531
532 return TRUE;
533 }
534
535 // suppress possible error messages
536 wxLogNull nolog;
537 wxRegKey key(wxRegKey::HKCR, m_strFileType);
538
539 if ( key.Open() ) {
540 // it's the default value of the key
541 if ( key.QueryValue(wxT(""), *desc) ) {
542 return TRUE;
543 }
544 }
545
546 return FALSE;
547 }
548
549 // helper function
550 wxFileType *
551 wxMimeTypesManagerImpl::CreateFileType(const wxString& filetype, const wxString& ext)
552 {
553 wxFileType *fileType = new wxFileType;
554 fileType->m_impl->Init(filetype, ext);
555 return fileType;
556 }
557
558 // extension -> file type
559 wxFileType *
560 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
561 {
562 // add the leading point if necessary
563 wxString str;
564 if ( ext[0u] != wxT('.') ) {
565 str = wxT('.');
566 }
567 str << ext;
568
569 // suppress possible error messages
570 wxLogNull nolog;
571
572 bool knownExtension = FALSE;
573
574 wxString strFileType;
575 wxRegKey key(wxRegKey::HKCR, str);
576 if ( key.Open() ) {
577 // it's the default value of the key
578 if ( key.QueryValue(wxT(""), strFileType) ) {
579 // create the new wxFileType object
580 return CreateFileType(strFileType, ext);
581 }
582 else {
583 // this extension doesn't have a filetype, but it's known to the
584 // system and may be has some other useful keys (open command or
585 // content-type), so still return a file type object for it
586 knownExtension = TRUE;
587 }
588 }
589
590 // check the fallbacks
591 // TODO linear search is potentially slow, perhaps we should use a sorted
592 // array?
593 size_t count = m_fallbacks.GetCount();
594 for ( size_t n = 0; n < count; n++ ) {
595 if ( m_fallbacks[n].GetExtensions().Index(ext) != wxNOT_FOUND ) {
596 wxFileType *fileType = new wxFileType;
597 fileType->m_impl->Init(m_fallbacks[n]);
598
599 return fileType;
600 }
601 }
602
603 if ( !knownExtension )
604 {
605 // unknown extension
606 return NULL;
607 }
608
609 return CreateFileType(wxEmptyString, ext);
610 }
611
612 wxFileType *
613 wxMimeTypesManagerImpl::GetOrAllocateFileTypeFromExtension(const wxString& ext)
614 {
615 wxFileType *fileType = GetFileTypeFromExtension(ext);
616 if ( !fileType )
617 {
618 fileType = CreateFileType(wxEmptyString, ext);
619 }
620
621 return fileType;
622 }
623
624
625 // MIME type -> extension -> file type
626 wxFileType *
627 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
628 {
629 wxString strKey = MIME_DATABASE_KEY;
630 strKey << mimeType;
631
632 // suppress possible error messages
633 wxLogNull nolog;
634
635 wxString ext;
636 wxRegKey key(wxRegKey::HKCR, strKey);
637 if ( key.Open() ) {
638 if ( key.QueryValue(wxT("Extension"), ext) ) {
639 return GetFileTypeFromExtension(ext);
640 }
641 }
642
643 // check the fallbacks
644 // TODO linear search is potentially slow, perhaps we should use a sorted
645 // array?
646 size_t count = m_fallbacks.GetCount();
647 for ( size_t n = 0; n < count; n++ ) {
648 if ( wxMimeTypesManager::IsOfType(mimeType,
649 m_fallbacks[n].GetMimeType()) ) {
650 wxFileType *fileType = new wxFileType;
651 fileType->m_impl->Init(m_fallbacks[n]);
652
653 return fileType;
654 }
655 }
656
657 // unknown MIME type
658 return NULL;
659 }
660
661 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
662 {
663 // enumerate all keys under MIME_DATABASE_KEY
664 wxRegKey key(wxRegKey::HKCR, MIME_DATABASE_KEY);
665
666 wxString type;
667 long cookie;
668 bool cont = key.GetFirstKey(type, cookie);
669 while ( cont )
670 {
671 mimetypes.Add(type);
672
673 cont = key.GetNextKey(type, cookie);
674 }
675
676 return mimetypes.GetCount();
677 }
678
679 // ----------------------------------------------------------------------------
680 // create a new association
681 // ----------------------------------------------------------------------------
682
683 wxFileType *wxMimeTypesManager::Associate(const wxString& ext,
684 const wxString& mimetype,
685 const wxString& filetypeOrig,
686 const wxString& WXUNUSED(desc))
687 {
688 wxCHECK_MSG( !ext.empty(), NULL, _T("Associate() needs extension") );
689
690 wxString extWithDot;
691 if ( ext[0u] != _T('.') )
692 extWithDot = _T('.');
693 extWithDot += ext;
694
695 wxRegKey key(wxRegKey::HKCR, extWithDot);
696 wxFileType *ft = NULL;
697 if ( !key.Exists() )
698 {
699 wxString filetype;
700
701 // create the mapping from the extension to the filetype
702 bool ok = key.Create();
703 if ( ok )
704 {
705 if ( filetypeOrig.empty() )
706 {
707 // make it up from the extension
708 filetype << extWithDot.c_str() + 1 << _T("_auto_file");
709 }
710 else
711 {
712 // just use the provided one
713 filetype = filetypeOrig;
714 }
715
716 ok = key.SetValue(_T(""), filetype);
717 }
718
719 if ( ok && !mimetype.empty() )
720 {
721 // set the MIME type
722 ok = key.SetValue(_T("Content Type"), mimetype);
723
724 if ( ok )
725 {
726 // create the MIME key
727 wxString strKey = MIME_DATABASE_KEY;
728 strKey << mimetype;
729 wxRegKey keyMIME(wxRegKey::HKCR, strKey);
730 ok = keyMIME.Create();
731
732 if ( ok )
733 {
734 // and provide a back link to the extension
735 ok = keyMIME.SetValue(_T("Extension"), extWithDot);
736 }
737 }
738 }
739
740 if ( ok )
741 {
742 // create the filetype key itself (it will be empty for now, but
743 // SetCommand(), SetDefaultIcon() &c will use it later)
744 wxRegKey keyFT(wxRegKey::HKCR, filetype);
745 ok = keyFT.Create();
746 }
747
748 if ( ok )
749 {
750 // ok, we've created everything correctly
751 ft = m_impl->CreateFileType(filetype, extWithDot);
752 }
753 else
754 {
755 // one of the registry operations failed
756 wxLogError(_("Failed to register extension '%s'."), ext.c_str());
757 }
758 }
759 else // key already exists
760 {
761 // FIXME we probably should return an existing file type then?
762 }
763
764 return ft;
765 }
766
767 #endif
768 // __WIN16__