]> git.saurik.com Git - wxWidgets.git/blob - src/mac/carbon/mimetmac.cpp
switch to a CoreFoundation based and thread-safe implementation for message boxes...
[wxWidgets.git] / src / mac / carbon / mimetmac.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: mac/mimetype.cpp
3 // Purpose: Mac Carbon implementation for wx mime-related classes
4 // Author: Ryan Norton
5 // Modified by:
6 // Created: 04/16/2005
7 // RCS-ID: $Id$
8 // Copyright: (c) 2005 Ryan Norton (<wxprojects@comcast.net>)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 //
13 //
14 // TODO: Search Info[-macos](classic).plist dictionary in addition
15 // to Internet Config database.
16 //
17 // Maybe try a brainstorm a way to change the wxMimeTypesManager API
18 // to get info from a file instead/addition to current get all stuff
19 // API so that we can use Launch Services to get mime type info.
20 //
21 // Implement geticon from one of the finder info functions - or
22 // use launch services and search that app's plist for the icon.
23 //
24 // Put some special juice in for the print command.
25 //
26 //
27 //
28
29 // for compilers that support precompilation, includes "wx.h".
30 #include "wx/wxprec.h"
31
32 #ifdef __BORLANDC__
33 #pragma hdrstop
34 #endif
35
36 #ifndef WX_PRECOMP
37 #include "wx/string.h"
38 #if wxUSE_GUI
39 #include "wx/icon.h"
40 #endif
41 #endif //WX_PRECOMP
42
43
44 #if wxUSE_MIMETYPE
45
46 #include "wx/log.h"
47 #include "wx/file.h"
48 #include "wx/intl.h"
49 #include "wx/dynarray.h"
50 #include "wx/confbase.h"
51
52 #include "wx/mac/mimetype.h"
53 #include "wx/mac/private.h" //wxMacMakeStringFromPascal
54
55 // other standard headers
56 #include <ctype.h>
57
58 #ifndef __DARWIN__
59 #include <InternetConfig.h> //For mime types
60 #endif
61
62 /* START CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) */
63
64 /* IsRemoteVolume can be used to find out if the
65 volume referred to by vRefNum is a remote volume
66 located somewhere on a network. the volume's attribute
67 flags (copied from the GetVolParmsInfoBuffer structure)
68 are returned in the longword pointed to by vMAttrib. */
69 OSErr IsRemoteVolume(short vRefNum, Boolean *isRemote, long *vMAttrib) {
70 HParamBlockRec volPB;
71 GetVolParmsInfoBuffer volinfo;
72 OSErr err;
73 volPB.ioParam.ioVRefNum = vRefNum;
74 volPB.ioParam.ioNamePtr = NULL;
75 volPB.ioParam.ioBuffer = (Ptr) &volinfo;
76 volPB.ioParam.ioReqCount = sizeof(volinfo);
77 err = PBHGetVolParmsSync(&volPB);
78 if (err == noErr) {
79 *isRemote = (volinfo.vMServerAdr != 0);
80 *vMAttrib = volinfo.vMAttrib;
81 }
82 return err;
83 }
84
85
86 /* BuildVolumeList fills the array pointed to by vols with
87 a list of the currently mounted volumes. If includeRemote
88 is true, then remote server volumes will be included in
89 the list. When remote server volumes are included in the
90 list, they will be added to the end of the list. On entry,
91 *count should contain the size of the array pointed to by
92 vols. On exit, *count will be set to the number of id numbers
93 placed in the array. If vMAttribMask is non-zero, then
94 only volumes with matching attributes are added to the
95 list of volumes. bits in the vMAttribMask should use the
96 same encoding as bits in the vMAttrib field of
97 the GetVolParmsInfoBuffer structure. */
98 OSErr BuildVolumeList(Boolean includeRemote, short *vols,
99 long *count, long vMAttribMask) {
100 HParamBlockRec volPB;
101 Boolean isRemote;
102 OSErr err = noErr;
103 long nlocal, nremote;
104 long vMAttrib;
105
106 /* set up and check parameters */
107 volPB.volumeParam.ioNamePtr = NULL;
108 nlocal = nremote = 0;
109 if (*count == 0) return noErr;
110
111 /* iterate through volumes */
112 for (volPB.volumeParam.ioVolIndex = 1;
113 PBHGetVInfoSync(&volPB) == noErr;
114 volPB.volumeParam.ioVolIndex++) {
115
116 /* skip remote volumes, if necessary */
117 err = IsRemoteVolume(volPB.volumeParam.ioVRefNum, &isRemote, &vMAttrib);
118 if (err != noErr) goto bail;
119 if ( ( includeRemote || ! isRemote )
120 && (vMAttrib & vMAttribMask) == vMAttribMask ) {
121
122 /* add local volumes at the front, remote
123 volumes at the end */
124 if (isRemote)
125 vols[nlocal + nremote++] = volPB.volumeParam.ioVRefNum;
126 else {
127 if (nremote > 0)
128 BlockMoveData(vols+nlocal, vols+nlocal+1,
129 nremote*sizeof(short));
130 vols[nlocal++] = volPB.volumeParam.ioVRefNum;
131 }
132
133 /* list full? */
134 if ((nlocal + nremote) >= *count) break;
135 }
136 }
137 bail:
138 *count = (nlocal + nremote);
139 return err;
140 }
141
142
143 /* FindApplication iterates through mounted volumes
144 searching for an application with the given creator
145 type. If includeRemote is true, then remote volumes
146 will be searched (after local ones) for an application
147 with the creator type. */
148
149 #define kMaxVols 20
150
151 /* Hacked to output to appName */
152
153 OSErr FindApplication(OSType appCreator, Boolean includeRemote, Str255 appName, FSSpec* appSpec) {
154 short rRefNums[kMaxVols];
155 long i, volCount;
156 DTPBRec desktopPB;
157 OSErr err;
158
159 /* get a list of volumes - with desktop files */
160 volCount = kMaxVols;
161 err = BuildVolumeList(includeRemote, rRefNums, &volCount,
162 (1<<bHasDesktopMgr) );
163 if (err != noErr) return err;
164
165 /* iterate through the list */
166 for (i=0; i<volCount; i++) {
167
168 /* has a desktop file? */
169 desktopPB.ioCompletion = NULL;
170 desktopPB.ioVRefNum = rRefNums[i];
171 desktopPB.ioNamePtr = NULL;
172 desktopPB.ioIndex = 0;
173 err = PBDTGetPath(&desktopPB);
174 if (err != noErr) continue;
175
176 /* has the correct app?? */
177 desktopPB.ioFileCreator = appCreator;
178 desktopPB.ioNamePtr = appName;
179 err = PBDTGetAPPLSync(&desktopPB);
180 if (err != noErr) continue;
181
182 /* make a file spec referring to it */
183 err = FSMakeFSSpec(rRefNums[i],
184 desktopPB.ioAPPLParID, appName,
185 appSpec);
186 if (err != noErr) continue;
187
188 /* found it! */
189 return noErr;
190
191 }
192 return fnfErr;
193 }
194
195 /* END CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) */
196
197 //yeah, duplicated code
198 pascal OSErr FSpGetFullPath(const FSSpec *spec,
199 short *fullPathLength,
200 Handle *fullPath)
201 {
202 OSErr result;
203 OSErr realResult;
204 FSSpec tempSpec;
205 CInfoPBRec pb;
206
207 *fullPathLength = 0;
208 *fullPath = NULL;
209
210
211 /* Default to noErr */
212 realResult = result = noErr;
213
214 /* work around Nav Services "bug" (it returns invalid FSSpecs with empty names) */
215 /*
216 if ( spec->name[0] == 0 )
217 {
218 result = FSMakeFSSpecCompat(spec->vRefNum, spec->parID, spec->name, &tempSpec);
219 }
220 else
221 {
222 */
223 /* Make a copy of the input FSSpec that can be modified */
224 BlockMoveData(spec, &tempSpec, sizeof(FSSpec));
225 /* }*/
226
227 if ( result == noErr )
228 {
229 if ( tempSpec.parID == fsRtParID )
230 {
231 /* The object is a volume */
232
233 /* Add a colon to make it a full pathname */
234 ++tempSpec.name[0];
235 tempSpec.name[tempSpec.name[0]] = ':';
236
237 /* We're done */
238 result = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]);
239 }
240 else
241 {
242 /* The object isn't a volume */
243
244 /* Is the object a file or a directory? */
245 pb.dirInfo.ioNamePtr = tempSpec.name;
246 pb.dirInfo.ioVRefNum = tempSpec.vRefNum;
247 pb.dirInfo.ioDrDirID = tempSpec.parID;
248 pb.dirInfo.ioFDirIndex = 0;
249 result = PBGetCatInfoSync(&pb);
250 /* Allow file/directory name at end of path to not exist. */
251 realResult = result;
252 if ( (result == noErr) || (result == fnfErr) )
253 {
254 /* if the object is a directory, append a colon so full pathname ends with colon */
255 if ( (result == noErr) && (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
256 {
257 ++tempSpec.name[0];
258 tempSpec.name[tempSpec.name[0]] = ':';
259 }
260
261 /* Put the object name in first */
262 result = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]);
263 if ( result == noErr )
264 {
265 /* Get the ancestor directory names */
266 pb.dirInfo.ioNamePtr = tempSpec.name;
267 pb.dirInfo.ioVRefNum = tempSpec.vRefNum;
268 pb.dirInfo.ioDrParID = tempSpec.parID;
269 do /* loop until we have an error or find the root directory */
270 {
271 pb.dirInfo.ioFDirIndex = -1;
272 pb.dirInfo.ioDrDirID = pb.dirInfo.ioDrParID;
273 result = PBGetCatInfoSync(&pb);
274 if ( result == noErr )
275 {
276 /* Append colon to directory name */
277 ++tempSpec.name[0];
278 tempSpec.name[tempSpec.name[0]] = ':';
279
280 /* Add directory name to beginning of fullPath */
281 (void) Munger(*fullPath, 0, NULL, 0, &tempSpec.name[1], tempSpec.name[0]);
282 result = MemError();
283 }
284 } while ( (result == noErr) && (pb.dirInfo.ioDrDirID != fsRtDirID) );
285 }
286 }
287 }
288 }
289
290 if ( result == noErr )
291 {
292 /* Return the length */
293 *fullPathLength = GetHandleSize(*fullPath);
294 result = realResult; /* return realResult in case it was fnfErr */
295 }
296 else
297 {
298 /* Dispose of the handle and return NULL and zero length */
299 if ( *fullPath != NULL )
300 {
301 DisposeHandle(*fullPath);
302 }
303 *fullPath = NULL;
304 *fullPathLength = 0;
305 }
306
307 return ( result );
308 }
309
310 //
311 // On the mac there are two ways to open a file - one is through apple events and the
312 // finder, another is through mime types.
313 //
314 // So, really there are two ways to implement wxFileType...
315 //
316 // Mime types are only available on OS 8.1+ through the InternetConfig API
317 //
318 // Much like the old-style file manager, it has 3 levels of flexibility for its methods -
319 // Low - which means you have to iterate yourself through the mime database
320 // Medium - which lets you sort of cache the database if you want to use lowlevel functions
321 // High - which requires access to the database every time
322 //
323 // We want to be efficient (i.e. professional :) ) about it, so we use a combo of low
324 // and mid-level functions
325 //
326 // TODO: Should we call ICBegin/ICEnd? Then where?
327 //
328
329 // debug helper
330 inline void wxLogMimeDebug(const wxChar* szMsg, OSStatus status)
331 {
332 wxLogDebug(wxString::Format(wxT("%s LINE:%i OSERROR:%i"), szMsg, __LINE__, (int)status));
333 }
334
335 // in case we're compiling in non-GUI mode
336 class WXDLLEXPORT wxIcon;
337
338 bool wxFileTypeImpl::SetCommand(const wxString& cmd, const wxString& verb, bool overwriteprompt)
339 {
340 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
341
342 return false;
343 }
344
345 bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int index)
346 {
347 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
348
349 return false;
350 }
351
352 bool wxFileTypeImpl::GetOpenCommand(wxString *openCmd,
353 const wxFileType::MessageParameters& params) const
354 {
355 wxString cmd = GetCommand(wxT("open"));
356
357 *openCmd = wxFileType::ExpandCommand(cmd, params);
358
359 return !openCmd->empty();
360 }
361
362 bool
363 wxFileTypeImpl::GetPrintCommand(wxString *printCmd,
364 const wxFileType::MessageParameters& params)
365 const
366 {
367 wxString cmd = GetCommand(wxT("print"));
368
369 *printCmd = wxFileType::ExpandCommand(cmd, params);
370
371 return !printCmd->empty();
372 }
373
374 //
375 // Internet Config vs. Launch Services
376 //
377 // From OS 8 on there was internet config...
378 // However, OSX and its finder does not use info
379 // from Internet Config at all - the Internet Config
380 // database ONLY CONTAINS APPS THAT ARE CLASSIC APPS
381 // OR REGISTERED THROUGH INTERNET CONFIG
382 //
383 // Therefore on OSX in order for the open command to be useful
384 // we need to go straight to launch services
385 //
386
387 #if defined(__DARWIN__)
388
389 //on darwin, use launch services
390 #include <ApplicationServices/ApplicationServices.h>
391
392 wxString wxFileTypeImpl::GetCommand(const wxString& verb) const
393 {
394 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
395
396 if(verb == wxT("open"))
397 {
398 ICMapEntry entry;
399 ICGetMapEntry( (ICInstance) m_manager->m_hIC,
400 (Handle) m_manager->m_hDatabase,
401 m_lIndex, &entry);
402
403 wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension);
404 sCurrentExtension = sCurrentExtension.Right(sCurrentExtension.Length()-1 );
405
406 //type, creator, ext, roles, outapp (FSRef), outappurl
407 CFURLRef cfurlAppPath;
408 OSStatus status = LSGetApplicationForInfo (kLSUnknownType,
409 kLSUnknownCreator,
410 wxMacCFStringHolder(sCurrentExtension, wxLocale::GetSystemEncoding()),
411 kLSRolesAll,
412 NULL,
413 &cfurlAppPath);
414
415 if(status == noErr)
416 {
417 CFStringRef cfsUnixPath = CFURLCopyFileSystemPath(cfurlAppPath, kCFURLPOSIXPathStyle);
418 CFRelease(cfurlAppPath);
419
420 //PHEW! Success!
421 if(cfsUnixPath)
422 return wxMacCFStringHolder(cfsUnixPath).AsString(wxLocale::GetSystemEncoding());
423 }
424 else
425 {
426 wxLogDebug(wxString::Format(wxT("%i - %s - %i"),
427 __LINE__,
428 wxT("LSGetApplicationForInfo failed."),
429 (int)status));
430 }
431 }
432
433 return wxEmptyString;
434 }
435
436 #else //carbon/classic implementation
437
438 wxString wxFileTypeImpl::GetCommand(const wxString& verb) const
439 {
440 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
441
442 if(verb == wxT("open"))
443 {
444 ICMapEntry entry;
445 ICGetMapEntry( (ICInstance) m_manager->m_hIC,
446 (Handle) m_manager->m_hDatabase,
447 m_lIndex, &entry);
448
449 //The entry in the mimetype database only contains the app
450 //that's registered - it may not exist... we need to remap the creator
451 //type and find the right application
452
453 // THIS IS REALLY COMPLICATED :\. There are a lot of conversions going
454 // on here.
455 Str255 outName;
456 FSSpec outSpec;
457 if(FindApplication(entry.fileCreator, false, outName, &outSpec) != noErr)
458 return wxEmptyString;
459
460 Handle outPathHandle;
461 short outPathSize;
462 OSErr err = FSpGetFullPath(&outSpec, &outPathSize, &outPathHandle);
463
464 if(err == noErr)
465 {
466 char* szPath = *outPathHandle;
467 wxString sClassicPath(szPath, wxConvLocal, outPathSize);
468 #if defined(__DARWIN__)
469 //Classic Path --> Unix (OSX) Path
470 CFURLRef finalURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
471 wxMacCFStringHolder(sClassicPath, wxLocale::GetSystemEncoding()),
472 kCFURLHFSPathStyle,
473 false); //false == not a directory
474
475 //clean up memory from the classic path handle
476 DisposeHandle(outPathHandle);
477
478 if(finalURL)
479 {
480 CFStringRef cfsUnixPath = CFURLCopyFileSystemPath(finalURL, kCFURLPOSIXPathStyle);
481 CFRelease(finalURL);
482
483 //PHEW! Success!
484 if(cfsUnixPath)
485 return wxMacCFStringHolder(cfsUnixPath).AsString(wxLocale::GetSystemEncoding());
486 }
487 #else //classic HFS path acceptable
488 return sClassicPath;
489 #endif
490 }
491 else
492 {
493 wxLogMimeDebug(wxT("FSpGetFullPath failed."), (OSStatus)err);
494 }
495 }
496 return wxEmptyString;
497 }
498 #endif //!DARWIN
499
500 bool wxFileTypeImpl::GetDescription(wxString *desc) const
501 {
502 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
503
504 ICMapEntry entry;
505 ICGetMapEntry( (ICInstance) m_manager->m_hIC,
506 (Handle) m_manager->m_hDatabase,
507 m_lIndex, &entry);
508
509 *desc = wxMacMakeStringFromPascal(entry.entryName);
510 return true;
511 }
512
513 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
514 {
515 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
516
517 ICMapEntry entry;
518 ICGetMapEntry( (ICInstance) m_manager->m_hIC,
519 (Handle) m_manager->m_hDatabase,
520 m_lIndex, &entry);
521
522 //entry has period in it
523 wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension);
524 extensions.Add( sCurrentExtension.Right(sCurrentExtension.Length()-1) );
525 return true;
526 }
527
528 bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const
529 {
530 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
531
532 ICMapEntry entry;
533 ICGetMapEntry( (ICInstance) m_manager->m_hIC,
534 (Handle) m_manager->m_hDatabase,
535 m_lIndex, &entry);
536
537 *mimeType = wxMacMakeStringFromPascal(entry.MIMEType);
538 return true;
539 }
540
541 bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
542 {
543 wxString s;
544
545 if (GetMimeType(&s))
546 {
547 mimeTypes.Clear();
548 mimeTypes.Add(s);
549 return true;
550 }
551 else
552 return false;
553 }
554
555 bool wxFileTypeImpl::GetIcon(wxIconLocation *WXUNUSED(icon)) const
556 {
557 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
558
559 // no such file type or no value or incorrect icon entry
560 return false;
561 }
562
563 size_t wxFileTypeImpl::GetAllCommands(wxArrayString * verbs, wxArrayString * commands,
564 const wxFileType::MessageParameters& params) const
565 {
566 wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") );
567
568 wxString sCommand;
569 size_t ulCount = 0;
570
571 if(GetOpenCommand(&sCommand, params))
572 {
573 verbs->Add(wxString(wxT("open")));
574 commands->Add(sCommand);
575 ++ulCount;
576 }
577
578 return ulCount;
579 }
580
581 void wxMimeTypesManagerImpl::Initialize(int mailcapStyles, const wxString& extraDir)
582 {
583 wxASSERT_MSG(m_hIC == NULL, wxT("Already initialized wxMimeTypesManager!"));
584
585 //some apps (non-wx) use the 'plst' resource instead
586 /*
587 CFBundleRef cfbMain = CFBundleGetMainBundle();
588 wxCFDictionary cfdInfo( CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN );
589 wxString sLog;
590 cfdInfo.PrintOut(sLog);
591 wxLogDebug(sLog);
592 */
593
594 //start internet config - log if there's an error
595 //the second param is the signature of the application, also known
596 //as resource ID 0. However, as per some recent discussions, we may not
597 //have a signature for this app, so a generic 'APPL' which is the executable
598 //type will work for now
599 OSStatus status = ICStart( (ICInstance*) &m_hIC, 'APPL');
600
601 if(status != noErr)
602 {
603 wxLogDebug(wxT("Could not initialize wxMimeTypesManager!"));
604 wxASSERT( false );
605 m_hIC = NULL;
606 return;
607 }
608
609 ICAttr attr;
610 m_hDatabase = (void**) NewHandle(0);
611 status = ICFindPrefHandle( (ICInstance) m_hIC, kICMapping, &attr, (Handle) m_hDatabase );
612
613 //the database file can be corrupt (on OSX its
614 //~/Library/Preferences/com.apple.internetconfig.plist)
615 //- bail if it is
616 if(status != noErr)
617 {
618 ClearData();
619 wxLogDebug(wxT("Corrupt Mime Database!"));
620 return;
621 }
622
623 //obtain the number of entries in the map
624 status = ICCountMapEntries( (ICInstance) m_hIC, (Handle) m_hDatabase, &m_lCount );
625 wxASSERT( status == noErr );
626 /*
627 //debug stuff
628 ICMapEntry entry;
629 long pos;
630
631 for(long i = 1; i <= m_lCount; ++i)
632 {
633 OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry);
634
635 if(status == noErr)
636 {
637 wxString sCreator = wxMacMakeStringFromPascal(entry.creatorAppName);
638 wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension);
639 wxString sMIMEType = wxMacMakeStringFromPascal(entry.MIMEType);
640
641 wxFileTypeImpl impl;
642 impl.Init(this, pos);
643
644 if(sMIMEType == wxT("text/html") && sCurrentExtension == wxT(".html"))
645 {
646 wxString cmd;
647 impl.GetOpenCommand (&cmd,
648 wxFileType::MessageParameters (wxT("http://www.google.com")));
649
650 wxPrintf(wxT("APP: [%s]\n"), cmd.c_str());
651 }
652 }
653 }
654 */
655 }
656
657 void wxMimeTypesManagerImpl::ClearData()
658 {
659 if(m_hIC != NULL)
660 {
661 DisposeHandle((Handle)m_hDatabase);
662 //this can return an error, but we don't really care that much about it
663 ICStop( (ICInstance) m_hIC );
664 m_hIC = NULL;
665 }
666 }
667
668 //
669 // Q) Iterating through the map - why does it use if (err == noErr) instead of just asserting?
670 // A) Some intermediate indexes are bad while subsequent ones may be good. Its wierd, I know.
671 //
672
673 // extension -> file type
674 wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& e)
675 {
676 wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") );
677
678 //low level functions - iterate through the database
679 ICMapEntry entry;
680 long pos;
681
682 for(long i = 1; i <= m_lCount; ++i)
683 {
684 OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry);
685
686 if(status == noErr)
687 {
688 wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension);
689 if( sCurrentExtension.Right(sCurrentExtension.Length()-1) == e ) //entry has period in it
690 {
691 wxFileType* pFileType = new wxFileType();
692 pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos);
693 return pFileType;
694 }
695 }
696 }
697
698 return NULL;
699 }
700
701 // MIME type -> extension -> file type
702 wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
703 {
704 wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") );
705
706 //low level functions - iterate through the database
707 ICMapEntry entry;
708 long pos;
709
710 for(long i = 1; i <= m_lCount; ++i)
711 {
712 OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry);
713 wxASSERT_MSG( status == noErr, wxString::Format(wxT("Error: %d"), (int)status) );
714
715 if(status == noErr)
716 {
717 if( wxMacMakeStringFromPascal(entry.MIMEType) == mimeType)
718 {
719 wxFileType* pFileType = new wxFileType();
720 pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos);
721 return pFileType;
722 }
723 }
724 }
725
726 return NULL;
727 }
728
729 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
730 {
731 wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") );
732
733 //low level functions - iterate through the database
734 ICMapEntry entry;
735 long pos;
736
737 long lStartCount = (long) mimetypes.GetCount();
738
739 for(long i = 1; i <= m_lCount; ++i)
740 {
741 OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry);
742 if( status == noErr )
743 mimetypes.Add( wxMacMakeStringFromPascal(entry.MIMEType) );
744 }
745
746 return mimetypes.GetCount() - lStartCount;
747 }
748
749
750 pascal OSStatus MoreProcGetProcessTypeSignature(
751 const ProcessSerialNumberPtr pPSN,
752 OSType *pProcessType,
753 OSType *pCreator)
754 {
755 OSStatus anErr = noErr;
756 ProcessInfoRec infoRec;
757 ProcessSerialNumber localPSN;
758
759 infoRec.processInfoLength = sizeof(ProcessInfoRec);
760 infoRec.processName = nil;
761 infoRec.processAppSpec = nil;
762
763 if ( pPSN == nil ) {
764 localPSN.highLongOfPSN = 0;
765 localPSN.lowLongOfPSN = kCurrentProcess;
766 } else {
767 localPSN = *pPSN;
768 }
769
770 anErr = GetProcessInformation(&localPSN, &infoRec);
771 if (anErr == noErr)
772 {
773 *pProcessType = infoRec.processType;
774 *pCreator = infoRec.processSignature;
775 }
776
777 return anErr;
778 }//end MoreProcGetProcessTypeSignature
779
780 //
781 //
782 // TODO: clean this up, its messy
783 //
784 //
785 //
786
787 #include "wx/mac/corefoundation/cfstring.h"
788 #include "wx/intl.h" //wxLocale for wxCFString
789
790 #define wxCF_RELEASE true
791 #define wxCF_RETAIN false
792
793 // ----------------------------------------------------------------------------
794 // wxCFDictionary
795 // ----------------------------------------------------------------------------
796
797 class wxCFDictionary
798 {
799 public:
800 wxCFDictionary(CFTypeRef ref, bool bRetain = wxCF_RELEASE)
801 {
802 m_cfmdRef = (CFMutableDictionaryRef) ref;
803 if(bRetain == wxCF_RETAIN && ref)
804 CFRetain(ref);
805 }
806
807 wxCFDictionary(CFIndex cfiSize = 0)
808 {
809 CFDictionaryKeyCallBacks kcbs;
810 CFDictionaryValueCallBacks vcbs;
811 BuildKeyCallbacks(&kcbs);
812 BuildValueCallbacks(&vcbs);
813
814 m_cfmdRef = CFDictionaryCreateMutable(
815 kCFAllocatorDefault, cfiSize, &kcbs, &vcbs);
816
817 }
818
819 ~wxCFDictionary()
820 { Clear(); }
821
822 void Clear()
823 {if(m_cfmdRef) CFRelease(m_cfmdRef);}
824
825 static const void* RetainProc(CFAllocatorRef, const void* v)
826 { return (const void*) CFRetain(v); }
827
828 static void ReleaseProc(CFAllocatorRef, const void* v)
829 { CFRelease(v); }
830
831 void MakeMutable(CFIndex cfiSize = 0)
832 {
833 CFDictionaryRef oldref = (CFDictionaryRef) m_cfmdRef;
834
835 m_cfmdRef = CFDictionaryCreateMutableCopy(
836 kCFAllocatorDefault,
837 cfiSize,
838 oldref);
839
840 CFRelease(oldref);
841 }
842
843 void BuildKeyCallbacks(CFDictionaryKeyCallBacks* pCbs)
844 {
845 pCbs->version = 0;
846 pCbs->retain = RetainProc;
847 pCbs->release = ReleaseProc;
848 pCbs->copyDescription = NULL;
849 pCbs->equal = NULL;
850 pCbs->hash = NULL;
851 }
852
853 void BuildValueCallbacks(CFDictionaryValueCallBacks* pCbs)
854 {
855 pCbs->version = 0;
856 pCbs->retain = RetainProc;
857 pCbs->release = ReleaseProc;
858 pCbs->copyDescription = NULL;
859 pCbs->equal = NULL;
860 }
861
862 operator CFTypeRef () const
863 { return (CFTypeRef)m_cfmdRef; }
864
865 CFDictionaryRef GetCFDictionary() const
866 { return (CFDictionaryRef)m_cfmdRef; }
867
868 CFMutableDictionaryRef GetCFMutableDictionary()
869 { return (CFMutableDictionaryRef) m_cfmdRef; }
870
871 CFTypeRef operator [] (CFTypeRef cftEntry) const
872 {
873 wxASSERT(IsValid());
874 return (CFTypeRef) CFDictionaryGetValue((CFDictionaryRef)m_cfmdRef, cftEntry);
875 }
876
877 CFIndex GetCount() const
878 {
879 wxASSERT(IsValid());
880 return CFDictionaryGetCount((CFDictionaryRef)m_cfmdRef);
881 }
882
883 void Add(CFTypeRef cftKey, CFTypeRef cftValue)
884 {
885 wxASSERT(IsValid());
886 wxASSERT(Exists(cftKey) == false);
887 CFDictionaryAddValue(m_cfmdRef, cftKey, cftValue);
888 }
889
890 void Remove(CFTypeRef cftKey)
891 {
892 wxASSERT(IsValid());
893 wxASSERT(Exists(cftKey));
894 CFDictionaryRemoveValue(m_cfmdRef, cftKey);
895 }
896
897 void Set(CFTypeRef cftKey, CFTypeRef cftValue)
898 {
899 wxASSERT(IsValid());
900 wxASSERT(Exists(cftKey));
901 CFDictionarySetValue(m_cfmdRef, cftKey, cftValue);
902 }
903
904 bool Exists(CFTypeRef cftKey) const
905 {
906 wxASSERT(IsValid());
907 return CFDictionaryContainsKey((CFDictionaryRef)m_cfmdRef, cftKey);
908 }
909
910 bool IsOk() const {return m_cfmdRef != NULL; }
911
912 bool IsValid() const
913 {
914 return IsOk() && CFGetTypeID((CFTypeRef)m_cfmdRef) == CFDictionaryGetTypeID();
915 }
916
917 void PrintOut(wxString& sMessage)
918 {
919 PrintOutDictionary(sMessage, m_cfmdRef);
920 }
921
922 static void PrintOutDictionary(wxString& sMessage, CFDictionaryRef cfdRef)
923 {
924 CFIndex cfiCount = CFDictionaryGetCount(cfdRef);
925 CFTypeRef* pKeys = new CFTypeRef[cfiCount];
926 CFTypeRef* pValues = new CFTypeRef[cfiCount];
927
928 CFDictionaryGetKeysAndValues(cfdRef, pKeys, pValues);
929
930 for(CFIndex i = 0; i < cfiCount; ++i)
931 {
932 wxString sKey = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID(pKeys[i]))).AsString();
933 wxString sValue = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID(pValues[i]))).AsString();
934
935 sMessage <<
936 wxString::Format(wxT("[{#%d} Key : %s]"), (int) i,
937 sKey.c_str());
938
939 PrintOutType(sMessage, sKey, pKeys[i]);
940
941 sMessage <<
942 wxString::Format(wxT("\n\t[Value : %s]"),
943 sValue.c_str());
944
945 PrintOutType(sMessage, sValue, pValues[i]);
946
947 sMessage << wxT("\n");
948 }
949
950 delete[] pKeys;
951 delete[] pValues;
952 }
953
954 static void PrintOutArray(wxString& sMessage, CFArrayRef cfaRef)
955 {
956
957 for(CFIndex i = 0; i < CFArrayGetCount(cfaRef); ++i)
958 {
959 wxString sValue = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID(
960 CFArrayGetValueAtIndex(cfaRef, i)
961 ))).AsString();
962
963 sMessage <<
964 wxString::Format(wxT("\t\t[{#%d} ArrayValue : %s]\n"), (int) i,
965 sValue.c_str());
966
967 PrintOutType(sMessage, sValue, CFArrayGetValueAtIndex(cfaRef, i));
968 }
969 }
970
971 static void PrintOutType(wxString& sMessage, const wxString& sValue, CFTypeRef cfRef)
972 {
973 sMessage << wxT(" {");
974
975 if(sValue == wxT("CFString"))
976 {
977 sMessage << wxMacCFStringHolder((CFStringRef)cfRef, false).AsString();
978 }
979 else if(sValue == wxT("CFNumber"))
980 {
981 int nOut;
982 CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, &nOut);
983 sMessage << nOut;
984 }
985 else if(sValue == wxT("CFDictionary"))
986 {
987 PrintOutDictionary(sMessage, (CFDictionaryRef)cfRef);
988 }
989 else if(sValue == wxT("CFArray"))
990 {
991 PrintOutArray(sMessage, (CFArrayRef)cfRef);
992 }
993 else if(sValue == wxT("CFBoolean"))
994 {
995 sMessage << (cfRef == kCFBooleanTrue ? wxT("true") : wxT("false"));
996 }
997 else if(sValue == wxT("CFURL"))
998 {
999 sMessage << wxMacCFStringHolder(CFURLCopyPath((CFURLRef) cfRef)).AsString();
1000 }
1001 else
1002 {
1003 sMessage << wxT("*****UNKNOWN TYPE******");
1004 }
1005
1006 sMessage << wxT("} ");
1007 }
1008
1009 #if wxUSE_MIMETYPE
1010 void MakeValidXML();
1011 #endif
1012
1013 CFTypeRef WriteAsXML()
1014 {
1015 return CFPropertyListCreateXMLData(kCFAllocatorDefault, m_cfmdRef);
1016 }
1017
1018 bool ReadAsXML(CFTypeRef cfData, wxString* pErrorMsg = NULL)
1019 {
1020 Clear();
1021 CFStringRef cfsError=NULL;
1022 m_cfmdRef = (CFMutableDictionaryRef) CFPropertyListCreateFromXMLData(
1023 kCFAllocatorDefault,
1024 (CFDataRef)cfData,
1025 kCFPropertyListMutableContainersAndLeaves,
1026 &cfsError );
1027 if(cfsError)
1028 {
1029 if(pErrorMsg)
1030 *pErrorMsg = wxMacCFStringHolder(cfsError).AsString();
1031 else
1032 CFRelease(cfsError);
1033 }
1034
1035 return m_cfmdRef != NULL;
1036 }
1037 private:
1038 CFMutableDictionaryRef m_cfmdRef;
1039 };
1040
1041 // ----------------------------------------------------------------------------
1042 // wxCFArray
1043 // ----------------------------------------------------------------------------
1044
1045 class wxCFArray
1046 {
1047 public:
1048 wxCFArray(CFTypeRef ref, bool bRetain = wxCF_RELEASE)
1049 {
1050 m_cfmaRef = (CFMutableArrayRef)ref;
1051 if(bRetain == wxCF_RETAIN && ref)
1052 CFRetain(ref);
1053 }
1054
1055 wxCFArray(CFIndex cfiSize = 0) : m_cfmaRef(NULL)
1056 { Create(cfiSize); }
1057
1058 ~wxCFArray()
1059 { Clear(); }
1060
1061 void MakeMutable(CFIndex cfiSize = 0)
1062 {
1063 wxASSERT(IsValid());
1064
1065 CFMutableArrayRef oldref = m_cfmaRef;
1066 m_cfmaRef = CFArrayCreateMutableCopy(
1067 kCFAllocatorDefault,
1068 cfiSize,
1069 (CFArrayRef)oldref);
1070 CFRelease(oldref);
1071 }
1072
1073 void BuildCallbacks(CFArrayCallBacks* pCbs)
1074 {
1075 pCbs->version = 0;
1076 pCbs->retain = RetainProc;
1077 pCbs->release = ReleaseProc;
1078 pCbs->copyDescription = NULL;
1079 pCbs->equal = NULL;
1080 }
1081
1082 void Create(CFIndex cfiSize = 0)
1083 {
1084 Clear();
1085 CFArrayCallBacks cb;
1086 BuildCallbacks(&cb);
1087
1088 m_cfmaRef = CFArrayCreateMutable(kCFAllocatorDefault, cfiSize, &cb);
1089 }
1090
1091 void Clear()
1092 {if(m_cfmaRef) CFRelease(m_cfmaRef);}
1093
1094 static const void* RetainProc(CFAllocatorRef, const void* v)
1095 { return (const void*) CFRetain(v); }
1096
1097 static void ReleaseProc(CFAllocatorRef, const void* v)
1098 { CFRelease(v); }
1099
1100 operator CFTypeRef () const
1101 { return (CFTypeRef)m_cfmaRef; }
1102
1103 CFArrayRef GetCFArray() const
1104 { return (CFArrayRef)m_cfmaRef; }
1105
1106 CFMutableArrayRef GetCFMutableArray()
1107 { return (CFMutableArrayRef) m_cfmaRef; }
1108
1109 CFTypeRef operator [] (CFIndex cfiIndex) const
1110 {
1111 wxASSERT(IsValid());
1112 return (CFTypeRef) CFArrayGetValueAtIndex((CFArrayRef)m_cfmaRef, cfiIndex);
1113 }
1114
1115 CFIndex GetCount()
1116 {
1117 wxASSERT(IsValid());
1118 return CFArrayGetCount((CFArrayRef)m_cfmaRef);
1119 }
1120
1121 void Add(CFTypeRef cftValue)
1122 {
1123 wxASSERT(IsValid());
1124 CFArrayAppendValue(m_cfmaRef, cftValue);
1125 }
1126
1127 void Remove(CFIndex cfiIndex)
1128 {
1129 wxASSERT(IsValid());
1130 wxASSERT(cfiIndex < GetCount());
1131 CFArrayRemoveValueAtIndex(m_cfmaRef, cfiIndex);
1132 }
1133
1134 void Set(CFIndex cfiIndex, CFTypeRef cftValue)
1135 {
1136 wxASSERT(IsValid());
1137 wxASSERT(cfiIndex < GetCount());
1138 CFArraySetValueAtIndex(m_cfmaRef, cfiIndex, cftValue);
1139 }
1140
1141 bool IsOk() const {return m_cfmaRef != NULL; }
1142
1143 bool IsValid() const
1144 {
1145 return IsOk() && CFGetTypeID((CFTypeRef)m_cfmaRef) == CFArrayGetTypeID();
1146 }
1147
1148 #if wxUSE_MIMETYPE
1149 void MakeValidXML();
1150 #endif
1151
1152 private:
1153 CFMutableArrayRef m_cfmaRef;
1154 };
1155
1156 // ----------------------------------------------------------------------------
1157 // wxCFString
1158 // ----------------------------------------------------------------------------
1159
1160 class wxCFString
1161 {
1162 public:
1163 wxCFString(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_Holder((CFStringRef)ref, bRetain == wxCF_RELEASE)
1164 { }
1165
1166 wxCFString(const wxChar* szString) : m_Holder(wxString(szString), wxLocale::GetSystemEncoding())
1167 { }
1168
1169 wxCFString(const wxString& sString) : m_Holder(sString, wxLocale::GetSystemEncoding())
1170 { }
1171
1172 operator CFTypeRef() const
1173 { return (CFTypeRef) ((CFStringRef) m_Holder); }
1174
1175 bool IsOk() { return ((CFTypeRef)(*this)) != NULL; }
1176
1177 wxString BuildWXString() {return m_Holder.AsString(); }
1178
1179 private:
1180 wxMacCFStringHolder m_Holder;
1181 };
1182
1183 // ----------------------------------------------------------------------------
1184 // wxCFNumber
1185 // ----------------------------------------------------------------------------
1186
1187 class wxCFNumber
1188 {
1189 public:
1190 wxCFNumber(int nValue)
1191 {
1192 m_cfnRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &nValue);
1193 }
1194
1195 wxCFNumber(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_cfnRef((CFNumberRef)ref)
1196 {
1197 if(bRetain == wxCF_RETAIN && ref)
1198 CFRetain(ref);
1199 }
1200
1201 ~wxCFNumber()
1202 {
1203 if(m_cfnRef)
1204 CFRelease(m_cfnRef);
1205 }
1206
1207
1208 operator CFTypeRef() const
1209 { return (CFTypeRef) m_cfnRef; }
1210
1211 int GetValue()
1212 {
1213 int nOut;
1214 CFNumberGetValue( m_cfnRef,
1215 kCFNumberIntType,
1216 &nOut
1217 );
1218
1219 return nOut;
1220 }
1221
1222 bool IsOk() { return m_cfnRef != NULL; }
1223
1224 private:
1225 CFNumberRef m_cfnRef;
1226 };
1227
1228 // ----------------------------------------------------------------------------
1229 // wxCFURL
1230 // ----------------------------------------------------------------------------
1231
1232 class wxCFURL
1233 {
1234 public:
1235 wxCFURL(CFTypeRef ref = NULL, bool bRetain = wxCF_RELEASE) : m_cfurlRef((CFURLRef)ref)
1236 {
1237 if(bRetain == wxCF_RETAIN && ref)
1238 CFRetain(ref);
1239 }
1240 wxCFURL(const wxCFString& URLString, CFTypeRef BaseURL = NULL)
1241 {
1242 Create(URLString, BaseURL);
1243 }
1244
1245 void Create(const wxCFString& URLString, CFTypeRef BaseURL = NULL)
1246 {
1247 m_cfurlRef = CFURLCreateWithString(
1248 kCFAllocatorDefault,
1249 (CFStringRef)(CFTypeRef)URLString,
1250 (CFURLRef) BaseURL);
1251 }
1252
1253 ~wxCFURL() {if(m_cfurlRef) CFRelease(m_cfurlRef);}
1254
1255 wxString BuildWXString()
1256 {
1257 return wxCFString(CFURLCopyPath(m_cfurlRef)).BuildWXString();
1258 }
1259
1260 operator CFTypeRef() const
1261 { return (CFTypeRef)m_cfurlRef; }
1262
1263 bool IsOk() { return m_cfurlRef != NULL; }
1264 private:
1265 CFURLRef m_cfurlRef;
1266 };
1267
1268 // ----------------------------------------------------------------------------
1269 // wxCFData
1270 // ----------------------------------------------------------------------------
1271
1272 #define wxCFDATA_RELEASEBUFFER 1
1273 #define wxCFDATA_RETAINBUFFER 0
1274
1275 class wxCFData
1276 {
1277 public:
1278 wxCFData(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_cfdaRef((CFDataRef)ref)
1279 {
1280 if(bRetain == wxCF_RETAIN && ref)
1281 CFRetain(ref);
1282 }
1283 wxCFData(const UInt8* pBytes, CFIndex len, bool bKeep = wxCFDATA_RELEASEBUFFER)
1284 {
1285 if(bKeep == wxCFDATA_RELEASEBUFFER)
1286 {
1287 m_cfdaRef = CFDataCreateWithBytesNoCopy
1288 (kCFAllocatorDefault, pBytes, len, kCFAllocatorDefault);
1289 }
1290 else
1291 {
1292 m_cfdaRef = CFDataCreate(kCFAllocatorDefault, pBytes, len);
1293 }
1294 }
1295 ~wxCFData() {if(m_cfdaRef) CFRelease(m_cfdaRef);}
1296
1297 const UInt8* GetValue()
1298 {
1299 return CFDataGetBytePtr(m_cfdaRef);
1300 }
1301
1302 CFIndex GetCount()
1303 {
1304 return CFDataGetLength(m_cfdaRef);
1305 }
1306
1307 operator CFTypeRef() const
1308 { return (CFTypeRef)m_cfdaRef; }
1309
1310 bool IsOk() { return m_cfdaRef != NULL; }
1311 private:
1312 CFDataRef m_cfdaRef;
1313 };
1314
1315 void wxCFDictionary::MakeValidXML()
1316 {
1317 CFIndex cfiCount = GetCount();
1318 CFTypeRef* pKeys = new CFTypeRef[cfiCount];
1319 CFTypeRef* pValues = new CFTypeRef[cfiCount];
1320
1321 CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues);
1322
1323 //for plist xml format all dictionary keys must be cfstrings and no values in
1324 //the dictionary or subkeys/values can be NULL
1325 //Also, CFURLs are not allowed
1326 for(CFIndex i = 0; i < cfiCount; ++i)
1327 {
1328 //must be an array, dictionary, string, bool, or int and cannot be null
1329 //and dictionaries can only contain cfstring keys
1330 CFTypeRef cfRef = pValues[i];
1331 if(!pKeys[i] ||
1332 CFGetTypeID(pKeys[i]) != CFStringGetTypeID() ||
1333 !cfRef)
1334 {
1335 Remove(pKeys[i]);
1336 --i;
1337 --cfiCount;
1338 delete[] pKeys;
1339 delete[] pValues;
1340 pKeys = new CFTypeRef[cfiCount];
1341 pValues = new CFTypeRef[cfiCount];
1342 CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues);
1343 }
1344 else if (CFGetTypeID(cfRef) == CFArrayGetTypeID())
1345 {
1346 CFRetain(cfRef);
1347 wxCFArray cfaCurrent(cfRef);
1348 cfaCurrent.MakeMutable();
1349 cfaCurrent.MakeValidXML();
1350 Set(pKeys[i], cfaCurrent);
1351 }
1352 else if (CFGetTypeID(cfRef) == CFDictionaryGetTypeID())
1353 {
1354 CFRetain(cfRef);
1355 wxCFDictionary cfdCurrent(cfRef);
1356 cfdCurrent.MakeMutable();
1357 cfdCurrent.MakeValidXML();
1358 Set(pKeys[i], cfdCurrent);
1359 }
1360 else if ( CFGetTypeID(cfRef) != CFStringGetTypeID() &&
1361 CFGetTypeID(cfRef) != CFNumberGetTypeID() &&
1362 CFGetTypeID(cfRef) != CFBooleanGetTypeID() )
1363 {
1364 Remove(pKeys[i]);
1365 --i;
1366 --cfiCount;
1367 delete[] pKeys;
1368 delete[] pValues;
1369 pKeys = new CFTypeRef[cfiCount];
1370 pValues = new CFTypeRef[cfiCount];
1371 CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues);
1372 }
1373 }
1374
1375 delete[] pValues;
1376 delete[] pKeys;
1377 }
1378
1379 void wxCFArray::MakeValidXML()
1380 {
1381 for(CFIndex i = 0; i < GetCount(); ++i)
1382 {
1383 //must be an array, dictionary, string, bool, or int and cannot be null
1384 //and dictionaries can only contain cfstring keys
1385 CFTypeRef cfRef = (*this)[i];
1386 if(!cfRef)
1387 {
1388 Remove(i);
1389 --i;
1390 }
1391 else if (CFGetTypeID(cfRef) == CFArrayGetTypeID())
1392 {
1393 CFRetain(cfRef);
1394 wxCFArray cfaCurrent(cfRef);
1395 cfaCurrent.MakeMutable();
1396 cfaCurrent.MakeValidXML();
1397 Set(i, cfaCurrent);
1398 }
1399 else if (CFGetTypeID(cfRef) == CFDictionaryGetTypeID())
1400 {
1401 CFRetain(cfRef);
1402 wxCFDictionary cfdCurrent(cfRef);
1403 cfdCurrent.MakeMutable();
1404 cfdCurrent.MakeValidXML();
1405 Set(i, cfdCurrent);
1406 }
1407 else if ( CFGetTypeID(cfRef) != CFStringGetTypeID() &&
1408 CFGetTypeID(cfRef) != CFNumberGetTypeID() &&
1409 CFGetTypeID(cfRef) != CFBooleanGetTypeID() )
1410 {
1411 Remove(i);
1412 --i;
1413 }
1414 }
1415 }
1416
1417 //
1418 //
1419 //
1420 // END TODO
1421 //
1422 //
1423 //
1424
1425 wxFileType* wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
1426 {
1427 bool bInfoSuccess = false;
1428
1429 const wxArrayString& asExtensions = ftInfo.GetExtensions();
1430 size_t dwFoundIndex = 0;
1431 if(!asExtensions.GetCount())
1432 {
1433 wxLogDebug(wxT("Must have extension to associate with"));
1434 }
1435
1436 //Find and write to Info.plist in main bundle (note that some other
1437 //apps have theirs named differently, i.e. IE's is named Info-macos.plist
1438 //some apps (non-wx) use the 'plst' resource instead
1439 CFBundleRef cfbMain = CFBundleGetMainBundle();
1440 if(cfbMain)
1441 {
1442 UInt32 dwBundleType, dwBundleCreator;
1443 CFBundleGetPackageInfo(cfbMain, &dwBundleType, &dwBundleCreator);
1444
1445 //if launching terminal non-app version will be 'BNDL' (generic bundle, maybe in other cases too),
1446 //which will give us the incorrect info.plist path
1447 //otherwise it will be 'APPL', or in the case of a framework,
1448 //'FMWK'
1449 if(dwBundleType == 'APPL')
1450 {
1451
1452 wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyBundleURL(cfbMain));
1453 // wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain));
1454 wxString sInfoPath;
1455 // sInfoPath << wxT("file://");
1456 sInfoPath << cfurlBundleLoc.BuildWXString();
1457 sInfoPath << wxT("Contents/Info.plist");
1458
1459 // wxCFDictionary cfdInfo( CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN );
1460 wxCFDictionary cfdInfo;
1461 bool bInfoOpenSuccess = false;
1462 wxFile indictfile;
1463 if(indictfile.Open(sInfoPath, wxFile::read))
1464 {
1465 CFIndex cfiBufLen = (CFIndex) indictfile.Length();
1466 const UInt8* pBuffer = new UInt8[cfiBufLen];
1467 indictfile.Read((void*)pBuffer, cfiBufLen);
1468 wxCFData cfdaInDict(pBuffer, cfiBufLen);
1469 wxString sError;
1470 bInfoOpenSuccess = cfdInfo.ReadAsXML(cfdaInDict, &sError);
1471 if(!bInfoOpenSuccess)
1472 wxLogDebug(sError);
1473 indictfile.Close();
1474 }
1475 if(bInfoOpenSuccess)
1476 {
1477 cfdInfo.MakeMutable( cfdInfo.GetCount() + 1 );
1478
1479 wxCFArray cfaDocTypes( cfdInfo[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN );
1480
1481 bool bAddDocTypesArrayToDictionary = cfaDocTypes.IsOk() == false;
1482 if(bAddDocTypesArrayToDictionary)
1483 cfaDocTypes.Create();
1484 else
1485 cfaDocTypes.MakeMutable( cfaDocTypes.GetCount() + 1 );
1486
1487 bool bEntryFound = false;
1488
1489 //search for duplicate
1490 CFIndex i;
1491 for(i = 0; i < cfaDocTypes.GetCount(); ++i)
1492 {
1493 wxCFDictionary cfdDocTypeEntry( cfaDocTypes[i], wxCF_RETAIN );
1494
1495 //A lot of apps dont do to mime types for some reason
1496 //so we go by extensions only
1497 wxCFArray cfaExtensions( cfdDocTypeEntry[ wxCFString(wxT("CFBundleTypeExtensions")) ],
1498 wxCF_RETAIN );
1499
1500 if(cfaExtensions.IsOk() == false)
1501 continue;
1502
1503 for(CFIndex iExt = 0; iExt < cfaExtensions.GetCount(); ++iExt)
1504 {
1505 for (size_t iWXExt = 0; iWXExt < asExtensions.GetCount(); ++iWXExt)
1506 {
1507 if(asExtensions[iWXExt] ==
1508 wxCFString(cfaExtensions[iExt], wxCF_RETAIN).BuildWXString())
1509 {
1510 bEntryFound = true;
1511 dwFoundIndex = iWXExt;
1512 break;
1513 }
1514 } //end of wxstring array
1515
1516 if(bEntryFound)
1517 break;
1518 } //end for cf array
1519
1520 if(bEntryFound)
1521 break;
1522 }//end for doctypes
1523
1524 wxCFDictionary cfdNewEntry;
1525
1526 if(!ftInfo.GetDescription().empty())
1527 {
1528 cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeName")),
1529 wxCFString(ftInfo.GetDescription()) );
1530 }
1531
1532 if(!ftInfo.GetIconFile().empty())
1533 {
1534 cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeIconFile")),
1535 wxCFString(ftInfo.GetIconFile()) );
1536 }
1537
1538
1539 wxCFArray cfaOSTypes;
1540 wxCFArray cfaExtensions;
1541 wxCFArray cfaMimeTypes;
1542
1543
1544 //OSTypes is a cfarray of four-char-codes - '****' for unrestricted
1545 cfaOSTypes.Add( wxCFString(wxT("****")) );
1546 cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeOSTypes")), cfaOSTypes );
1547
1548 if(ftInfo.GetExtensionsCount() != 0) //'*' for unrestricted
1549 {
1550 for(size_t iExtension = 0; iExtension < (size_t)ftInfo.GetExtensionsCount(); ++iExtension)
1551 {
1552 cfaExtensions.Add( wxCFString( asExtensions[iExtension] ) );
1553 }
1554
1555 cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeExtensions")), cfaExtensions );
1556 }
1557
1558 if(!ftInfo.GetMimeType().empty())
1559 {
1560 cfaMimeTypes.Add( wxCFString(ftInfo.GetMimeType()) );
1561 cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeMIMETypes")), cfaMimeTypes );
1562 }
1563
1564 // Editor - can perform all actions
1565 // Viewer - all actions except manipulation/saving
1566 // None - can perform no actions
1567 cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeRole")), wxCFString(wxT("Editor")) );
1568
1569 // Is application bundled?
1570 cfdNewEntry.Add( wxCFString(wxT("LSTypeIsPackage")), kCFBooleanTrue );
1571
1572 if(bEntryFound)
1573 cfaDocTypes.Set(i, cfdNewEntry);
1574 else
1575 cfaDocTypes.Add(cfdNewEntry);
1576
1577 //
1578 // set the doc types array in the muted dictionary
1579 //
1580
1581 if(bAddDocTypesArrayToDictionary)
1582 cfdInfo.Add(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes);
1583 else
1584 cfdInfo.Set(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes);
1585
1586 cfdInfo.MakeValidXML();
1587
1588 wxFile outdictfile;
1589 if(outdictfile.Open(sInfoPath, wxFile::write))
1590 {
1591 wxCFData cfdaInfo(cfdInfo.WriteAsXML());
1592 if(cfdaInfo.IsOk())
1593 {
1594 if(outdictfile.Write(cfdaInfo.GetValue(), cfdaInfo.GetCount()) !=
1595 (wxFileOffset)cfdaInfo.GetCount())
1596 {
1597 wxLogDebug(wxT("error in writing to file"));
1598 }
1599 else
1600 {
1601 bInfoSuccess = true;
1602 //#if defined(__DARWIN__)
1603 // //force launch services to update its database for the finder
1604 // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true);
1605 // if(status != noErr)
1606 // {
1607 // wxLogDebug(wxT("LSRegisterURL Failed."));
1608 // }
1609 //#endif
1610 }
1611 outdictfile.Close();
1612 }
1613 else
1614 {
1615 outdictfile.Close();
1616 wxLogDebug(wxT("Could not read in new dictionary"));
1617 }
1618 }
1619 else
1620 {
1621 wxLogDebug(wxString(wxT("Could not open [")) +
1622 sInfoPath + wxT("] for writing."));
1623 }
1624 }
1625 else
1626 {
1627 wxLogDebug(wxT("No info dictionary in main bundle"));
1628 }
1629 }
1630 else
1631 {
1632 wxLogDebug(wxT("Can only call associate from bundled app within XXX.app"));
1633 }
1634 }
1635 else
1636 {
1637 wxLogDebug(wxT("No main bundle"));
1638 }
1639
1640 #if defined(__DARWIN__)
1641 if(!bInfoSuccess)
1642 return NULL;
1643 #endif
1644 //on mac you have to embed it into the mac's file reference resource ('FREF' I believe)
1645 //or, alternately, you could just add an entry to m_hDatabase, but you'd need to get
1646 //the app's signature somehow...
1647
1648 OSType processType,
1649 creator;
1650 OSStatus status = MoreProcGetProcessTypeSignature(NULL,&processType, &creator);
1651
1652 if(status == noErr)
1653 {
1654 Str255 psCreatorName;
1655 FSSpec dummySpec;
1656 status = FindApplication(creator, false, psCreatorName, &dummySpec);
1657
1658 if(status == noErr)
1659 {
1660
1661 //get the file type if it exists -
1662 //if it really does then modify the database then save it,
1663 //otherwise we need to create a whole new entry
1664 wxFileType* pFileType = GetFileTypeFromExtension(asExtensions[dwFoundIndex]);
1665 if(pFileType)
1666 {
1667 ICMapEntry entry;
1668 ICGetMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase,
1669 pFileType->m_impl->m_lIndex, &entry);
1670
1671 memcpy(entry.creatorAppName, psCreatorName, sizeof(Str255));
1672 entry.fileCreator = creator;
1673
1674 status = ICSetMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase,
1675 pFileType->m_impl->m_lIndex, &entry);
1676
1677 //success
1678 if(status == noErr)
1679 {
1680 //kICAttrNoChange means we don't care about attributes such as
1681 //locking in the database
1682 // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping,
1683 // kICAttrNoChange, (Handle) m_hDatabase);
1684 // if(status == noErr)
1685 return pFileType;
1686 // else
1687 // {
1688 // wxLogDebug(wxString::Format(wxT("%i - %s"), (int)status, wxT("ICSetPrefHandle failed.")));
1689 // }
1690 }
1691 else
1692 {
1693 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetMapEntry failed.")));
1694 }
1695
1696 //failure - cleanup
1697 delete pFileType;
1698 }
1699 else
1700 {
1701 //TODO: Maybe force all 3 of these to be non-empty?
1702 Str255 psExtension;
1703 Str255 psMimeType;
1704 Str255 psDescription;
1705
1706 wxMacStringToPascal(wxString(wxT(".")) + ftInfo.GetExtensions()[0], psExtension);
1707 wxMacStringToPascal(ftInfo.GetMimeType(), psMimeType);
1708 wxMacStringToPascal(ftInfo.GetDescription(), psDescription);
1709
1710 Str255 psPostCreatorName;
1711 wxMacStringToPascal(wxT(""), psPostCreatorName);
1712
1713
1714 //add the entry to the database
1715 ICMapEntry entry;
1716 entry.totalLength = sizeof(ICMapEntry);
1717 entry.fixedLength = kICMapFixedLength;
1718 entry.version = 0;
1719 entry.fileType = 0; //TODO: File type?
1720 entry.fileCreator = creator;
1721 entry.postCreator = 0;
1722 entry.flags = kICMapDataForkBit; //TODO: Maybe resource is valid by default too?
1723 PLstrcpy( entry.extension , psExtension ) ;
1724 memcpy(entry.creatorAppName, psCreatorName, sizeof(Str255));
1725 memcpy(entry.postAppName, psPostCreatorName, sizeof(Str255));
1726 memcpy(entry.MIMEType, psMimeType, sizeof(Str255));
1727 memcpy(entry.entryName, psDescription, sizeof(Str255));
1728
1729 status = ICAddMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, &entry);
1730
1731 if(status == noErr)
1732 {
1733 //kICAttrNoChange means we don't care about attributes such as
1734 //locking in the database
1735 // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping,
1736 // kICAttrNoChange, (Handle) m_hDatabase);
1737
1738 //return the entry in the database if successful
1739 // if(status == noErr)
1740 return GetFileTypeFromExtension(ftInfo.GetMimeType());
1741 // else
1742 // {
1743 // wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed.")));
1744 // }
1745 }
1746 else
1747 {
1748 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICAppMapEntry failed.")));
1749 }
1750 }
1751 } //end if FindApplcation was successful
1752 else
1753 {
1754 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("FindApplication failed.")));
1755 }
1756 } //end if it could obtain app's signature
1757 else
1758 {
1759 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("GetProcessSignature failed.")));
1760 }
1761 return NULL;
1762 }
1763
1764 bool
1765 wxMimeTypesManagerImpl::Unassociate(wxFileType *pFileType)
1766 {
1767 wxASSERT(pFileType);
1768 bool bInfoSuccess = false;
1769
1770 wxArrayString asExtensions;
1771 pFileType->GetExtensions(asExtensions);
1772
1773 if(!asExtensions.GetCount())
1774 {
1775 wxLogDebug(wxT("Must have extension to disassociate"));
1776 return false;
1777 }
1778
1779 //Find and write to Info.plist in main bundle (note that some other
1780 //apps have theirs named differently, i.e. IE's is named Info-macos.plist
1781 //some apps (non-wx) use the 'plst' resource instead
1782 CFBundleRef cfbMain = CFBundleGetMainBundle();
1783 if(cfbMain)
1784 {
1785 UInt32 dwBundleType, dwBundleCreator;
1786 CFBundleGetPackageInfo(cfbMain, &dwBundleType, &dwBundleCreator);
1787
1788 //if launching terminal non-app version will be 'BNDL' (generic bundle, maybe in other cases too),
1789 //which will give us the incorrect info.plist path
1790 //otherwise it will be 'APPL', or in the case of a framework,
1791 //'FMWK'
1792 if(dwBundleType == 'APPL')
1793 {
1794
1795 wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyBundleURL(cfbMain));
1796 // wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain));
1797 wxString sInfoPath;
1798 // sInfoPath << wxT("file://");
1799 sInfoPath << cfurlBundleLoc.BuildWXString();
1800 sInfoPath << wxT("Contents/Info.plist");
1801
1802 // wxCFDictionary cfdInfo( (CFTypeRef) CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN );
1803 wxCFDictionary cfdInfo;
1804 bool bInfoOpenSuccess = false;
1805 wxFile indictfile;
1806 if(indictfile.Open(sInfoPath, wxFile::read))
1807 {
1808 CFIndex cfiBufLen = (CFIndex) indictfile.Length();
1809 const UInt8* pBuffer = new UInt8[cfiBufLen];
1810 indictfile.Read((void*)pBuffer, cfiBufLen);
1811 wxCFData cfdaInDict(pBuffer, cfiBufLen);
1812 wxString sError;
1813 bInfoOpenSuccess = cfdInfo.ReadAsXML(cfdaInDict, &sError);
1814 if(!bInfoOpenSuccess)
1815 wxLogDebug(sError);
1816 indictfile.Close();
1817 }
1818 if(bInfoOpenSuccess)
1819 {
1820 cfdInfo.MakeMutable( cfdInfo.GetCount() + 1 );
1821
1822 wxCFArray cfaDocTypes( cfdInfo[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN );
1823
1824 if(cfaDocTypes.IsOk())
1825 {
1826 bool bEntryFound = false;
1827
1828 //search for duplicate
1829 CFIndex i;
1830 for(i = 0; i < cfaDocTypes.GetCount(); ++i)
1831 {
1832 wxCFDictionary cfdDocTypeEntry( cfaDocTypes[i], wxCF_RETAIN );
1833
1834 //A lot of apps dont do to mime types for some reason
1835 //so we go by extensions only
1836 wxCFArray cfaExtensions( cfdDocTypeEntry[ wxCFString(wxT("CFBundleTypeExtensions")) ],
1837 wxCF_RETAIN );
1838
1839 if(cfaExtensions.IsOk() == false)
1840 continue;
1841
1842 for(CFIndex iExt = 0; iExt < cfaExtensions.GetCount(); ++iExt)
1843 {
1844 for (size_t iWXExt = 0; iWXExt < asExtensions.GetCount(); ++iWXExt)
1845 {
1846 if(asExtensions[iWXExt] ==
1847 wxCFString(cfaExtensions[iExt], wxCF_RETAIN).BuildWXString())
1848 {
1849 bEntryFound = true;
1850 cfaDocTypes.Remove(i);
1851 cfdInfo.Set( wxCFString(wxT("CFBundleDocumentTypes")) , cfaDocTypes );
1852 break;
1853 }
1854 } //end of wxstring array
1855
1856 if(bEntryFound)
1857 break;
1858 } //end for cf array
1859
1860 if(bEntryFound)
1861 break;
1862 }//end for doctypes
1863
1864 if(bEntryFound)
1865 {
1866 cfdInfo.MakeValidXML();
1867
1868 wxFile outdictfile;
1869 if(outdictfile.Open(sInfoPath, wxFile::write))
1870 {
1871 wxCFData cfdaInfo(cfdInfo.WriteAsXML());
1872 if(cfdaInfo.IsOk())
1873 {
1874 if(outdictfile.Write(cfdaInfo.GetValue(), cfdaInfo.GetCount()) !=
1875 (wxFileOffset)cfdaInfo.GetCount())
1876 {
1877 wxLogDebug(wxT("error in writing to file"));
1878 }
1879 else
1880 {
1881 bInfoSuccess = true;
1882 //#if defined(__DARWIN__)
1883 // //force launch services to update its database for the finder
1884 // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true);
1885 // if(status != noErr)
1886 // {
1887 // wxLogDebug(wxT("LSRegisterURL Failed."));
1888 // }
1889 //#endif
1890 }
1891 outdictfile.Close();
1892 }
1893 else
1894 {
1895 outdictfile.Close();
1896 wxLogDebug(wxT("Could not read in new dictionary"));
1897 }
1898 }
1899 else
1900 {
1901 wxLogDebug(wxString(wxT("Could not open [")) +
1902 sInfoPath + wxT("] for writing."));
1903 }
1904 }
1905 else
1906 {
1907 wxLogDebug(wxT("Entry not found to remove"));
1908 wxString sPrintOut;
1909 wxCFDictionary::PrintOutArray(sPrintOut, (CFArrayRef)(CFTypeRef)cfaDocTypes);
1910 wxLogDebug(sPrintOut);
1911 for(size_t i = 0; i < asExtensions.GetCount(); ++i)
1912 wxLogDebug(asExtensions[i]);
1913 }
1914 }
1915 else
1916 {
1917 wxLogDebug(wxT("No doc types array found"));
1918 wxString sPrintOut; cfdInfo.PrintOut(sPrintOut); wxLogDebug(sPrintOut);
1919 }
1920 }
1921 else
1922 {
1923 wxLogDebug(wxT("No info dictionary in main bundle"));
1924 }
1925 }
1926 else
1927 {
1928 wxLogDebug(wxT("Can only call associate from bundled app within XXX.app"));
1929 }
1930 }
1931 else
1932 {
1933 wxLogDebug(wxT("No main bundle"));
1934 }
1935
1936 #if defined(__DARWIN__)
1937 if(!bInfoSuccess)
1938 return false;
1939 #endif
1940
1941 //this should be as easy as removing the entry from the database and then saving
1942 //the database
1943 OSStatus status = ICDeleteMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase,
1944 pFileType->m_impl->m_lIndex);
1945
1946 if(status == noErr)
1947 {
1948 //kICAttrNoChange means we don't care about attributes such as
1949 //locking in the database
1950 // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping,
1951 // kICAttrNoChange, (Handle) m_hDatabase);
1952
1953 // if(status == noErr)
1954 return true;
1955 // else
1956 // {
1957 // wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed.")));
1958 // }
1959
1960 }
1961 else
1962 {
1963 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICDeleteMapEntry failed.")));
1964 }
1965
1966 return false;
1967 }
1968 /*
1969 CFWriteStreamRef cfwsInfo = CFWriteStreamCreateWithFile(
1970 kCFAllocatorDefault,
1971 (CFURLRef) (CFTypeRef)cfurlInfoLoc );
1972
1973 // CFShow(cfdInfo);
1974 if(cfwsInfo)
1975 {
1976 Boolean bOpened = CFWriteStreamOpen(cfwsInfo);
1977 if(bOpened)
1978 {
1979 CFStringRef cfsError;
1980 CFIndex cfiWritten = CFPropertyListWriteToStream((CFPropertyListRef)(CFTypeRef)cfdInfo,
1981 cfwsInfo,
1982 kCFPropertyListXMLFormat_v1_0, //100
1983 &cfsError);
1984 if(cfsError && cfiWritten == 0)
1985 {
1986 wxLogDebug(wxCFString(cfsError).BuildWXString());
1987 wxString sMessage;
1988 cfdInfo.PrintOut(sMessage);
1989 wxLogDebug(sMessage);
1990 }
1991 else
1992 {
1993 bInfoSuccess = true;
1994 //#if defined(__DARWIN__)
1995 // //force launch services to update its database for the finder
1996 // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true);
1997 // if(status != noErr)
1998 // {
1999 // wxLogDebug(wxT("LSRegisterURL Failed."));
2000 // }
2001 //#endif
2002 }
2003
2004 CFWriteStreamClose(cfwsInfo);
2005
2006 */
2007 #endif //wxUSE_MIMETYPE