]>
Commit | Line | Data |
---|---|---|
7dc3cc31 VS |
1 | ///////////////////////////////////////////////////////////////////////////// |
2 | // Name: mac/mimetype.cpp | |
c9e227f4 RN |
3 | // Purpose: Mac Carbon implementation for wx mime-related classes |
4 | // Author: Ryan Norton | |
7dc3cc31 | 5 | // Modified by: |
c9e227f4 | 6 | // Created: 04/16/2005 |
7dc3cc31 | 7 | // RCS-ID: $Id$ |
c9e227f4 RN |
8 | // Copyright: (c) 2005 Ryan Norton (<wxprojects@comcast.net>) |
9 | // Licence: wxWindows licence | |
7dc3cc31 VS |
10 | ///////////////////////////////////////////////////////////////////////////// |
11 | ||
3d1a4878 | 12 | #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) |
7dc3cc31 VS |
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 | ||
7dc3cc31 VS |
23 | #ifndef WX_PRECOMP |
24 | #include "wx/string.h" | |
25 | #if wxUSE_GUI | |
26 | #include "wx/icon.h" | |
27 | #endif | |
28 | #endif //WX_PRECOMP | |
29 | ||
30 | ||
4b8f9d46 RN |
31 | #if wxUSE_MIMETYPE |
32 | ||
7dc3cc31 VS |
33 | #include "wx/log.h" |
34 | #include "wx/file.h" | |
35 | #include "wx/intl.h" | |
36 | #include "wx/dynarray.h" | |
37 | #include "wx/confbase.h" | |
38 | ||
39 | #include "wx/mac/mimetype.h" | |
c9e227f4 | 40 | #include "wx/mac/private.h" //wxMacMakeStringFromPascal |
7dc3cc31 VS |
41 | |
42 | // other standard headers | |
43 | #include <ctype.h> | |
c9e227f4 RN |
44 | #include <InternetConfig.h> //For mime types |
45 | ||
46 | // | |
47 | // On the mac there are two ways to open a file - one is through apple events and the | |
48 | // finder, another is through mime types. | |
49 | // | |
50 | // So, really there are two ways to implement wxFileType... | |
51 | // | |
52 | // Mime types are only available on OS 8.1+ through the InternetConfig API | |
53 | // | |
54 | // Much like the old-style file manager, it has 3 levels of flexibility for its methods - | |
55 | // Low - which means you have to iterate yourself through the mime database | |
56 | // Medium - which lets you sort of cache the database if you want to use lowlevel functions | |
57 | // High - which requires access to the database every time | |
58 | // | |
59 | // We want to be efficient (i.e. professional :) ) about it, so we use a combo of low | |
60 | // and mid-level functions | |
61 | // | |
62 | // TODO: Should we call ICBegin/ICEnd? Then where? | |
63 | // | |
7dc3cc31 VS |
64 | |
65 | // in case we're compiling in non-GUI mode | |
66 | class WXDLLEXPORT wxIcon; | |
67 | ||
f040060e GD |
68 | bool wxFileTypeImpl::SetCommand(const wxString& cmd, const wxString& verb, bool overwriteprompt) |
69 | { | |
70 | return FALSE; | |
71 | } | |
72 | ||
73 | bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int index) | |
74 | { | |
75 | return FALSE; | |
76 | } | |
7dc3cc31 | 77 | |
c9e227f4 RN |
78 | bool wxFileTypeImpl::GetOpenCommand(wxString *openCmd, |
79 | const wxFileType::MessageParameters& params) const | |
7dc3cc31 | 80 | { |
c9e227f4 RN |
81 | wxString cmd = GetCommand(wxT("open")); |
82 | ||
83 | *openCmd = wxFileType::ExpandCommand(cmd, params); | |
84 | ||
85 | return !openCmd->empty(); | |
7dc3cc31 VS |
86 | } |
87 | ||
c9e227f4 RN |
88 | bool |
89 | wxFileTypeImpl::GetPrintCommand(wxString *printCmd, | |
90 | const wxFileType::MessageParameters& params) | |
91 | const | |
7dc3cc31 | 92 | { |
c9e227f4 RN |
93 | wxString cmd = GetCommand(wxT("print")); |
94 | ||
95 | *printCmd = wxFileType::ExpandCommand(cmd, params); | |
96 | ||
97 | return !printCmd->empty(); | |
7dc3cc31 VS |
98 | } |
99 | ||
c9e227f4 RN |
100 | /* START CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) */ |
101 | ||
102 | /* IsRemoteVolume can be used to find out if the | |
103 | volume referred to by vRefNum is a remote volume | |
104 | located somewhere on a network. the volume's attribute | |
105 | flags (copied from the GetVolParmsInfoBuffer structure) | |
106 | are returned in the longword pointed to by vMAttrib. */ | |
107 | OSErr IsRemoteVolume(short vRefNum, Boolean *isRemote, long *vMAttrib) { | |
108 | HParamBlockRec volPB; | |
109 | GetVolParmsInfoBuffer volinfo; | |
110 | OSErr err; | |
111 | volPB.ioParam.ioVRefNum = vRefNum; | |
112 | volPB.ioParam.ioNamePtr = NULL; | |
113 | volPB.ioParam.ioBuffer = (Ptr) &volinfo; | |
114 | volPB.ioParam.ioReqCount = sizeof(volinfo); | |
115 | err = PBHGetVolParmsSync(&volPB); | |
116 | if (err == noErr) { | |
117 | *isRemote = (volinfo.vMServerAdr != 0); | |
118 | *vMAttrib = volinfo.vMAttrib; | |
119 | } | |
120 | return err; | |
121 | } | |
122 | ||
123 | ||
124 | /* BuildVolumeList fills the array pointed to by vols with | |
125 | a list of the currently mounted volumes. If includeRemote | |
126 | is true, then remote server volumes will be included in | |
127 | the list. When remote server volumes are included in the | |
128 | list, they will be added to the end of the list. On entry, | |
129 | *count should contain the size of the array pointed to by | |
130 | vols. On exit, *count will be set to the number of id numbers | |
131 | placed in the array. If vMAttribMask is non-zero, then | |
132 | only volumes with matching attributes are added to the | |
133 | list of volumes. bits in the vMAttribMask should use the | |
134 | same encoding as bits in the vMAttrib field of | |
135 | the GetVolParmsInfoBuffer structure. */ | |
136 | OSErr BuildVolumeList(Boolean includeRemote, short *vols, | |
137 | long *count, long vMAttribMask) { | |
138 | HParamBlockRec volPB; | |
139 | Boolean isRemote; | |
140 | OSErr err; | |
141 | long nlocal, nremote; | |
142 | long vMAttrib; | |
143 | ||
144 | /* set up and check parameters */ | |
145 | volPB.volumeParam.ioNamePtr = NULL; | |
146 | nlocal = nremote = 0; | |
147 | if (*count == 0) return noErr; | |
148 | ||
149 | /* iterate through volumes */ | |
150 | for (volPB.volumeParam.ioVolIndex = 1; | |
151 | PBHGetVInfoSync(&volPB) == noErr; | |
152 | volPB.volumeParam.ioVolIndex++) { | |
153 | ||
154 | /* skip remote volumes, if necessary */ | |
155 | err = IsRemoteVolume(volPB.volumeParam.ioVRefNum, &isRemote, &vMAttrib); | |
156 | if (err != noErr) goto bail; | |
157 | if ( ( includeRemote || ! isRemote ) | |
158 | && (vMAttrib & vMAttribMask) == vMAttribMask ) { | |
159 | ||
160 | /* add local volumes at the front, remote | |
161 | volumes at the end */ | |
162 | if (isRemote) | |
163 | vols[nlocal + nremote++] = volPB.volumeParam.ioVRefNum; | |
164 | else { | |
165 | if (nremote > 0) | |
166 | BlockMoveData(vols+nlocal, vols+nlocal+1, | |
167 | nremote*sizeof(short)); | |
168 | vols[nlocal++] = volPB.volumeParam.ioVRefNum; | |
169 | } | |
170 | ||
171 | /* list full? */ | |
172 | if ((nlocal + nremote) >= *count) break; | |
173 | } | |
174 | } | |
175 | bail: | |
176 | *count = (nlocal + nremote); | |
177 | return err; | |
178 | } | |
179 | ||
180 | ||
181 | /* FindApplication iterates through mounted volumes | |
182 | searching for an application with the given creator | |
183 | type. If includeRemote is true, then remote volumes | |
184 | will be searched (after local ones) for an application | |
185 | with the creator type. */ | |
186 | ||
187 | #define kMaxVols 20 | |
188 | ||
189 | /* Hacked to output to appName */ | |
190 | ||
191 | OSErr FindApplication(OSType appCreator, Boolean includeRemote, Str255 appName) { | |
192 | short rRefNums[kMaxVols]; | |
193 | long i, volCount; | |
194 | DTPBRec desktopPB; | |
195 | OSErr err; | |
196 | FSSpec realappSpec; | |
197 | FSSpec *appSpec = &realappSpec; | |
198 | ||
199 | /* get a list of volumes - with desktop files */ | |
200 | volCount = kMaxVols; | |
201 | err = BuildVolumeList(includeRemote, rRefNums, &volCount, | |
202 | (1<<bHasDesktopMgr) ); | |
203 | if (err != noErr) return err; | |
204 | ||
205 | /* iterate through the list */ | |
206 | for (i=0; i<volCount; i++) { | |
207 | ||
208 | /* has a desktop file? */ | |
209 | desktopPB.ioCompletion = NULL; | |
210 | desktopPB.ioVRefNum = rRefNums[i]; | |
211 | desktopPB.ioNamePtr = NULL; | |
212 | desktopPB.ioIndex = 0; | |
213 | err = PBDTGetPath(&desktopPB); | |
214 | if (err != noErr) continue; | |
215 | ||
216 | /* has the correct app?? */ | |
217 | desktopPB.ioFileCreator = appCreator; | |
218 | desktopPB.ioNamePtr = appName; | |
219 | err = PBDTGetAPPLSync(&desktopPB); | |
220 | if (err != noErr) continue; | |
221 | ||
222 | /* make a file spec referring to it */ | |
223 | err = FSMakeFSSpec(rRefNums[i], | |
224 | desktopPB.ioAPPLParID, appName, | |
225 | appSpec); | |
226 | if (err != noErr) continue; | |
227 | ||
228 | /* found it! */ | |
229 | return noErr; | |
230 | ||
231 | } | |
232 | return fnfErr; | |
233 | } | |
234 | ||
235 | /* END CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) */ | |
236 | ||
237 | wxString wxFileTypeImpl::GetCommand(const wxString& verb) const | |
7dc3cc31 | 238 | { |
c9e227f4 RN |
239 | if(!m_manager) |
240 | return wxEmptyString; | |
241 | ||
242 | if(verb == wxT("open")) | |
7dc3cc31 | 243 | { |
c9e227f4 RN |
244 | ICMapEntry entry; |
245 | OSStatus status = ICGetMapEntry( (ICInstance) m_manager->m_hIC, | |
246 | (Handle) m_manager->m_hDatabase, | |
247 | m_lIndex, &entry); | |
248 | wxASSERT( status == noErr ); | |
249 | ||
250 | //Technote 1002 is a godsend in launching apps :) | |
251 | //The entry in the mimetype database only contains the app | |
252 | //that's registered - it may not exist... we need to remap the creator | |
253 | //type and find the right application | |
254 | Str255 outName; | |
255 | if(FindApplication(entry.fileCreator, false, outName) != noErr) | |
256 | return wxEmptyString; | |
257 | ||
258 | //TODO: this is only partially correct - | |
259 | //it should go to the os-specific application path folder (using wxStdPaths maybe?), | |
260 | //then go to the bundled app and return that full path | |
261 | return wxMacMakeStringFromPascal(outName); | |
7dc3cc31 | 262 | } |
c9e227f4 RN |
263 | return wxEmptyString; |
264 | } | |
265 | ||
266 | bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) | |
267 | { | |
268 | if(!m_manager) | |
269 | return false; | |
270 | ||
271 | ICMapEntry entry; | |
272 | OSStatus status = ICGetMapEntry( (ICInstance) m_manager->m_hIC, | |
273 | (Handle) m_manager->m_hDatabase, | |
274 | m_lIndex, &entry); | |
275 | wxASSERT( status == noErr ); | |
276 | ||
277 | //entry has period in it | |
278 | wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); | |
279 | extensions.Add( sCurrentExtension.Right(sCurrentExtension.Length()-1) ); | |
280 | return true; | |
281 | } | |
282 | ||
283 | bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const | |
284 | { | |
285 | if(!m_manager) | |
286 | return false; | |
287 | ||
288 | ICMapEntry entry; | |
289 | OSStatus status = ICGetMapEntry( (ICInstance) m_manager->m_hIC, | |
290 | (Handle) m_manager->m_hDatabase, | |
291 | m_lIndex, &entry); | |
292 | wxASSERT( status == noErr ); | |
293 | ||
294 | *mimeType = wxMacMakeStringFromPascal(entry.MIMEType); | |
295 | return true; | |
7dc3cc31 VS |
296 | } |
297 | ||
4d2976ad VS |
298 | bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const |
299 | { | |
300 | wxString s; | |
301 | ||
302 | if (GetMimeType(&s)) | |
303 | { | |
304 | mimeTypes.Clear(); | |
305 | mimeTypes.Add(s); | |
306 | return TRUE; | |
307 | } | |
308 | else | |
309 | return FALSE; | |
310 | } | |
311 | ||
da0766ab | 312 | bool wxFileTypeImpl::GetIcon(wxIconLocation *WXUNUSED(icon)) const |
7dc3cc31 VS |
313 | { |
314 | // no such file type or no value or incorrect icon entry | |
315 | return FALSE; | |
316 | } | |
317 | ||
318 | bool wxFileTypeImpl::GetDescription(wxString *desc) const | |
319 | { | |
c9e227f4 RN |
320 | if(!m_manager) |
321 | return false; | |
322 | ||
323 | ICMapEntry entry; | |
324 | OSStatus status = ICGetMapEntry( (ICInstance) m_manager->m_hIC, | |
325 | (Handle) m_manager->m_hDatabase, | |
326 | m_lIndex, &entry); | |
327 | wxASSERT( status == noErr ); | |
328 | ||
329 | *desc = wxString((char*)entry.entryName, wxConvLocal); | |
330 | return true; | |
7dc3cc31 VS |
331 | } |
332 | ||
c9e227f4 | 333 | size_t wxFileTypeImpl::GetAllCommands(wxArrayString * verbs, wxArrayString * commands, |
e40298d5 | 334 | const wxFileType::MessageParameters& params) const |
007ae8d0 | 335 | { |
0fe3b231 | 336 | wxFAIL_MSG( _T("wxFileTypeImpl::GetAllCommands() not yet implemented") ); |
f040060e GD |
337 | return 0; |
338 | } | |
339 | ||
c9e227f4 | 340 | void wxMimeTypesManagerImpl::Initialize(int mailcapStyles, const wxString& extraDir) |
f040060e | 341 | { |
c9e227f4 | 342 | wxASSERT_MSG(m_hIC == NULL, wxT("Already initialized wxMimeTypesManager!")); |
007ae8d0 | 343 | |
c9e227f4 RN |
344 | //start internet config - log if there's an error |
345 | //the second param is the signature of the application, also known | |
346 | //as resource ID 0. However, as per some recent discussions, we may not | |
347 | //have a signature for this app, so a generic 'APPL' which is the executable | |
348 | //type will work for now | |
349 | OSStatus status = ICStart( (ICInstance*) &m_hIC, 'APPL'); | |
350 | ||
351 | if(status != noErr) | |
7dc3cc31 | 352 | { |
c9e227f4 RN |
353 | wxLogSysError(wxT("Could not initialize wxMimeTypesManager!")); |
354 | wxASSERT( false ); | |
355 | return; | |
7dc3cc31 | 356 | } |
c9e227f4 RN |
357 | |
358 | ICAttr attr; | |
359 | m_hDatabase = (void**) NewHandle(0); | |
360 | status = ICFindPrefHandle( (ICInstance) m_hIC, kICMapping, &attr, (Handle) m_hDatabase ); | |
361 | ||
362 | //the database file can be corrupt (on OSX its | |
363 | //~/Library/Preferences/com.apple.internetconfig.plist) | |
364 | //- bail if it is | |
365 | if(status != noErr) | |
7dc3cc31 | 366 | { |
c9e227f4 RN |
367 | ClearData(); |
368 | wxLogSysError(wxT("Bad Mime Database!")); | |
369 | return; | |
7dc3cc31 | 370 | } |
c9e227f4 RN |
371 | |
372 | //obtain the number of entries in the map | |
373 | status = ICCountMapEntries( (ICInstance) m_hIC, (Handle) m_hDatabase, &m_lCount ); | |
374 | wxASSERT( status == noErr ); | |
375 | } | |
376 | ||
377 | void wxMimeTypesManagerImpl::ClearData() | |
378 | { | |
379 | if(m_hIC != NULL) | |
7dc3cc31 | 380 | { |
c9e227f4 RN |
381 | DisposeHandle((Handle)m_hDatabase); |
382 | //this can return an error, but we don't really care that much about it | |
383 | ICStop( (ICInstance) m_hIC ); | |
384 | m_hIC = NULL; | |
7dc3cc31 | 385 | } |
c9e227f4 RN |
386 | } |
387 | ||
388 | // extension -> file type | |
389 | wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& e) | |
390 | { | |
391 | wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); | |
392 | ||
393 | // ICMapEntry entry; | |
394 | // OSStatus status = ICMapEntriesFilename( (ICInstance) m_hIC, (Handle) m_hDatabase, | |
395 | // (unsigned char*) e.mb_str(wxConvLocal), &entry ); | |
396 | ||
397 | // if (status != noErr) | |
398 | // return NULL; //err or ext not known | |
399 | //low level functions - iterate through the database | |
400 | ICMapEntry entry; | |
401 | wxFileType* pFileType; | |
402 | long pos; | |
403 | ||
404 | for(long i = 1; i <= m_lCount; ++i) | |
7dc3cc31 | 405 | { |
c9e227f4 RN |
406 | OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry); |
407 | ||
408 | if(status == noErr) | |
409 | { | |
410 | wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); | |
411 | if( sCurrentExtension.Right(sCurrentExtension.Length()-1) == e ) //entry has period in it | |
412 | { | |
413 | pFileType = new wxFileType(); | |
414 | pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos); | |
415 | break; | |
416 | } | |
417 | } | |
7dc3cc31 | 418 | } |
c9e227f4 RN |
419 | |
420 | return pFileType; | |
7dc3cc31 VS |
421 | } |
422 | ||
423 | // MIME type -> extension -> file type | |
c9e227f4 | 424 | wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) |
7dc3cc31 | 425 | { |
c9e227f4 RN |
426 | wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); |
427 | ||
428 | //low level functions - iterate through the database | |
429 | ICMapEntry entry; | |
430 | wxFileType* pFileType; | |
431 | ||
432 | long pos; | |
433 | ||
434 | for(long i = 1; i <= m_lCount; ++i) | |
435 | { | |
436 | OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry); | |
437 | wxASSERT_MSG( status == noErr, wxString::Format(wxT("Error: %d"), (int)status) ); | |
438 | ||
439 | if(status == noErr) | |
440 | { | |
441 | if( wxMacMakeStringFromPascal(entry.MIMEType) == mimeType) | |
442 | { | |
443 | pFileType = new wxFileType(); | |
444 | pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos); | |
445 | break; | |
446 | } | |
447 | } | |
448 | } | |
449 | ||
450 | return pFileType; | |
7dc3cc31 VS |
451 | } |
452 | ||
453 | size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes) | |
454 | { | |
c9e227f4 | 455 | wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); |
7dc3cc31 | 456 | |
c9e227f4 RN |
457 | //low level functions - iterate through the database |
458 | ICMapEntry entry; | |
459 | ||
460 | long pos; | |
461 | ||
462 | for(long i = 1; i <= m_lCount; ++i) | |
463 | { | |
464 | OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry); | |
465 | if( status == noErr ) | |
466 | mimetypes.Add( wxMacMakeStringFromPascal(entry.MIMEType) ); | |
467 | } | |
468 | ||
469 | return m_lCount; | |
7dc3cc31 VS |
470 | } |
471 | ||
007ae8d0 GD |
472 | wxFileType * |
473 | wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo) | |
474 | { | |
0fe3b231 | 475 | wxFAIL_MSG( _T("wxMimeTypesManagerImpl::Associate() not yet implemented") ); |
c9e227f4 RN |
476 | //on mac you have to embed it into the mac's file reference resource ('FREF' I believe) |
477 | //or, alternately, you could just add an entry to m_hDatabase, but you'd need to get | |
478 | //the app's signature somehow... | |
007ae8d0 GD |
479 | |
480 | return NULL; | |
481 | } | |
f040060e GD |
482 | |
483 | bool | |
484 | wxMimeTypesManagerImpl::Unassociate(wxFileType *ft) | |
485 | { | |
c9e227f4 RN |
486 | //this should be as easy as removing the entry from the database and then saving |
487 | //the database | |
f040060e GD |
488 | return FALSE; |
489 | } | |
490 | ||
4b8f9d46 | 491 | #endif //wxUSE_MIMETYPE |