X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/f040060e2fdaf0a5cc20f986fbbc67a000f5f57b..59abfcf813ccca7366bfb6d830e1251be5514ac7:/src/mac/carbon/mimetmac.cpp diff --git a/src/mac/carbon/mimetmac.cpp b/src/mac/carbon/mimetmac.cpp index ddf62412cc..f1ce7743e5 100644 --- a/src/mac/carbon/mimetmac.cpp +++ b/src/mac/carbon/mimetmac.cpp @@ -1,17 +1,27 @@ ///////////////////////////////////////////////////////////////////////////// // Name: mac/mimetype.cpp -// Purpose: classes and functions to manage MIME types -// Author: Vadim Zeitlin +// Purpose: Mac Carbon implementation for wx MIME-related classes +// Author: Ryan Norton // Modified by: -// Created: 23.09.98 +// Created: 04/16/2005 // RCS-ID: $Id$ -// Copyright: (c) 1998 Vadim Zeitlin -// Licence: wxWindows license (part of wxExtra library) +// Copyright: (c) 2005 Ryan Norton () +// Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#ifdef __GNUG__ -#pragma implementation "mimetype.h" -#endif +// +// TODO: Search Info[-macos](classic).plist dictionary in addition +// to Internet Config database. +// +// Maybe try a brainstorm a way to change the wxMimeTypesManager API +// to get info from a file instead/addition to current get all stuff +// API so that we can use Launch Services to get MIME type info. +// +// Implement GetIcon from one of the FinderInfo functions - or +// use Launch Services and search that app's plist for the icon. +// +// Put some special juice in for the print command. +// // for compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -20,18 +30,17 @@ #pragma hdrstop #endif -#ifndef WX_PRECOMP - #include "wx/defs.h" -#endif - #ifndef WX_PRECOMP #include "wx/string.h" + #if wxUSE_GUI #include "wx/icon.h" #endif -#endif //WX_PRECOMP +#endif +#if wxUSE_MIMETYPE + #include "wx/log.h" #include "wx/file.h" #include "wx/intl.h" @@ -39,189 +48,2011 @@ #include "wx/confbase.h" #include "wx/mac/mimetype.h" +#include "wx/mac/private.h" // other standard headers #include +#ifndef __DARWIN__ + #include + #include +#endif + + +// START CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) + +// IsRemoteVolume can be used to find out if the +// volume referred to by vRefNum is a remote volume +// located somewhere on a network. the volume's attribute +// flags (copied from the GetVolParmsInfoBuffer structure) +// are returned in the longword pointed to by vMAttrib. +OSErr IsRemoteVolume(short vRefNum, Boolean *isRemote, long *vMAttrib) +{ + HParamBlockRec volPB; + GetVolParmsInfoBuffer volinfo; + OSErr err; + + volPB.ioParam.ioVRefNum = vRefNum; + volPB.ioParam.ioNamePtr = NULL; + volPB.ioParam.ioBuffer = (Ptr)&volinfo; + volPB.ioParam.ioReqCount = sizeof(volinfo); + err = PBHGetVolParmsSync( &volPB ); + if (err == noErr) + { + *isRemote = (volinfo.vMServerAdr != 0); + *vMAttrib = volinfo.vMAttrib; + } + + return err; +} + +// BuildVolumeList fills the array pointed to by vols with +// a list of the currently mounted volumes. If includeRemote +// is true, then remote server volumes will be included in +// the list. When remote server volumes are included in the +// list, they will be added to the end of the list. On entry, +// *count should contain the size of the array pointed to by +// vols. On exit, *count will be set to the number of id numbers +// placed in the array. If vMAttribMask is non-zero, then +// only volumes with matching attributes are added to the +// list of volumes. bits in the vMAttribMask should use the +// same encoding as bits in the vMAttrib field of +// the GetVolParmsInfoBuffer structure. +OSErr BuildVolumeList(Boolean includeRemote, short *vols, + long *count, long vMAttribMask) +{ + HParamBlockRec volPB; + Boolean isRemote; + OSErr err = noErr; + long nlocal, nremote; + long vMAttrib; + + // set up and check parameters + volPB.volumeParam.ioNamePtr = NULL; + nlocal = nremote = 0; + if (*count == 0) + return noErr; + + // iterate through volumes + for (volPB.volumeParam.ioVolIndex = 1; + PBHGetVInfoSync(&volPB) == noErr; + volPB.volumeParam.ioVolIndex++) + { + // skip remote volumes, if necessary + err = IsRemoteVolume(volPB.volumeParam.ioVRefNum, &isRemote, &vMAttrib); + if (err != noErr) + goto bail; + + if ((includeRemote || !isRemote) && ((vMAttrib & vMAttribMask) == vMAttribMask)) + { + // add local volumes at the front; remote volumes at the end + if (isRemote) + vols[nlocal + nremote++] = volPB.volumeParam.ioVRefNum; + else + { + if (nremote > 0) + BlockMoveData( + vols + nlocal, + vols + nlocal + 1, + nremote * sizeof(short) ); + vols[nlocal++] = volPB.volumeParam.ioVRefNum; + } + + // list full? + if ((nlocal + nremote) >= *count) + break; + } + } + +bail: + *count = (nlocal + nremote); + + return err; +} + + +// FindApplication iterates through mounted volumes +// searching for an application with the given creator +// type. If includeRemote is true, then remote volumes +// will be searched (after local ones) for an application +// with the creator type. +// +// Hacked to output to appName +// +#define kMaxVols 20 + +OSErr FindApplication(OSType appCreator, Boolean includeRemote, Str255 appName, FSSpec* appSpec) +{ + short rRefNums[kMaxVols]; + long i, volCount; + DTPBRec desktopPB; + OSErr err; + + // get a list of volumes - with desktop files + volCount = kMaxVols; + err = BuildVolumeList(includeRemote, rRefNums, &volCount, (1 << bHasDesktopMgr) ); + if (err != noErr) + return err; + + // iterate through the list + for (i=0; iname[0] == 0 ) + { + result = FSMakeFSSpecCompat(spec->vRefNum, spec->parID, spec->name, &tempSpec); + } + else + { +#endif + + // Make a copy of the input FSSpec that can be modified + BlockMoveData( spec, &tempSpec, sizeof(FSSpec) ); + + if ( result == noErr ) + { + if ( tempSpec.parID == fsRtParID ) + { + // object is a volume + // Add a colon to make it a full pathname + ++tempSpec.name[0]; + tempSpec.name[tempSpec.name[0]] = ':'; + + // We're done + result = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]); + } + else + { + // object isn't a volume + + // Is the object a file or a directory? + pb.dirInfo.ioNamePtr = tempSpec.name; + pb.dirInfo.ioVRefNum = tempSpec.vRefNum; + pb.dirInfo.ioDrDirID = tempSpec.parID; + pb.dirInfo.ioFDirIndex = 0; + result = PBGetCatInfoSync( &pb ); + + // Allow file/directory name at end of path to not exist. + realResult = result; + if ((result == noErr) || (result == fnfErr)) + { + // if the object is a directory, append a colon so full pathname ends with colon + if ((result == noErr) && (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0) + { + ++tempSpec.name[0]; + tempSpec.name[tempSpec.name[0]] = ':'; + } + + // Put the object name in first + result = PtrToHand( &tempSpec.name[1], fullPath, tempSpec.name[0] ); + if ( result == noErr ) + { + // Get the ancestor directory names + pb.dirInfo.ioNamePtr = tempSpec.name; + pb.dirInfo.ioVRefNum = tempSpec.vRefNum; + pb.dirInfo.ioDrParID = tempSpec.parID; + + // loop until we have an error or find the root directory + do + { + pb.dirInfo.ioFDirIndex = -1; + pb.dirInfo.ioDrDirID = pb.dirInfo.ioDrParID; + result = PBGetCatInfoSync(&pb); + if ( result == noErr ) + { + // Append colon to directory name + ++tempSpec.name[0]; + tempSpec.name[tempSpec.name[0]] = ':'; + + // Add directory name to beginning of fullPath + (void)Munger(*fullPath, 0, NULL, 0, &tempSpec.name[1], tempSpec.name[0]); + result = MemError(); + } + } + while ( (result == noErr) && (pb.dirInfo.ioDrDirID != fsRtDirID) ); + } + } + } + } + + if ( result == noErr ) + { + // Return the length + *fullPathLength = GetHandleSize( *fullPath ); + result = realResult; // return realResult in case it was fnfErr + } + else + { + // Dispose of the handle and return NULL and zero length + if ( *fullPath != NULL ) + { + DisposeHandle( *fullPath ); + *fullPath = NULL; + } + *fullPathLength = 0; + } + + return result; +} + +// +// On the mac there are two ways to open a file - one is through apple events and the +// finder, another is through mime types. +// +// So, really there are two ways to implement wxFileType... +// +// Mime types are only available on OS 8.1+ through the InternetConfig API +// +// Much like the old-style file manager, it has 3 levels of flexibility for its methods - +// Low - which means you have to iterate yourself through the mime database +// Medium - which lets you sort of cache the database if you want to use lowlevel functions +// High - which requires access to the database every time +// +// We want to be efficient (i.e. professional :) ) about it, so we use a combo of low +// and mid-level functions +// +// TODO: Should we call ICBegin/ICEnd? Then where? +// + +// debug helper +inline void wxLogMimeDebug(const wxChar* szMsg, OSStatus status) +{ + wxLogDebug(wxString::Format(wxT("%s LINE:%i OSERROR:%i"), szMsg, __LINE__, (int)status)); +} + // in case we're compiling in non-GUI mode class WXDLLEXPORT wxIcon; bool wxFileTypeImpl::SetCommand(const wxString& cmd, const wxString& verb, bool overwriteprompt) { - return FALSE; + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + return false; } bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int index) { - return FALSE; + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + return false; } -bool wxFileTypeImpl::GetCommand(wxString *command, const char *verb) const +bool wxFileTypeImpl::GetOpenCommand(wxString *openCmd, + const wxFileType::MessageParameters& params) const { - return FALSE; + wxString cmd = GetCommand(wxT("open")); + + *openCmd = wxFileType::ExpandCommand(cmd, params); + + return !openCmd->empty(); } -// @@ this function is half implemented -bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) +bool +wxFileTypeImpl::GetPrintCommand( + wxString *printCmd, + const wxFileType::MessageParameters& params) const { - return FALSE; + wxString cmd = GetCommand(wxT("print")); + + *printCmd = wxFileType::ExpandCommand(cmd, params); + + return !printCmd->empty(); } -bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const +// +// Internet Config vs. Launch Services +// +// From OS 8 on there was internet config... +// However, OSX and its finder does not use info +// from Internet Config at all - the Internet Config +// database ONLY CONTAINS APPS THAT ARE CLASSIC APPS +// OR REGISTERED THROUGH INTERNET CONFIG +// +// Therefore on OSX in order for the open command to be useful +// we need to go straight to launch services +// + +#if defined(__DARWIN__) + +//on darwin, use launch services +#include + +wxString wxFileTypeImpl::GetCommand(const wxString& verb) const +{ + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + if (verb == wxT("open")) + { + ICMapEntry entry; + ICGetMapEntry( (ICInstance) m_manager->m_hIC, + (Handle) m_manager->m_hDatabase, + m_lIndex, &entry); + + wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); + sCurrentExtension = sCurrentExtension.Right(sCurrentExtension.Length()-1 ); + + //type, creator, ext, roles, outapp (FSRef), outappurl + CFURLRef cfurlAppPath; + OSStatus status = LSGetApplicationForInfo( kLSUnknownType, + kLSUnknownCreator, + wxMacCFStringHolder(sCurrentExtension, wxLocale::GetSystemEncoding()), + kLSRolesAll, + NULL, + &cfurlAppPath ); + + if (status == noErr) + { + CFStringRef cfsUnixPath = CFURLCopyFileSystemPath(cfurlAppPath, kCFURLPOSIXPathStyle); + CFRelease(cfurlAppPath); + + // PHEW! Success! + // Since a filename might have spaces in it, so surround it with quotes + if (cfsUnixPath) + { + wxString resultStr; + + resultStr = + wxString(wxT("'")) + + wxMacCFStringHolder(cfsUnixPath).AsString(wxLocale::GetSystemEncoding()) + + wxString(wxT("'")); + + return resultStr; + } + } + else + { + wxLogDebug(wxString::Format(wxT("%i - %s - %i"), + __LINE__, + wxT("LSGetApplicationForInfo failed."), + (int)status)); + } + } + + return wxEmptyString; +} + +#else //carbon/classic implementation + +wxString wxFileTypeImpl::GetCommand(const wxString& verb) const { - if ( m_strFileType.Length() > 0 ) + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + if (verb == wxT("open")) { - *mimeType = m_strFileType ; - return TRUE ; + ICMapEntry entry; + ICGetMapEntry( (ICInstance) m_manager->m_hIC, + (Handle) m_manager->m_hDatabase, + m_lIndex, &entry); + + //The entry in the mimetype database only contains the app + //that's registered - it may not exist... we need to remap the creator + //type and find the right application + + // THIS IS REALLY COMPLICATED :\. + // There are a lot of conversions going on here. + Str255 outName; + FSSpec outSpec; + OSErr err = FindApplication( entry.fileCreator, false, outName, &outSpec ); + if (err != noErr) + return wxEmptyString; + + Handle outPathHandle; + short outPathSize; + err = FSpGetFullPath( &outSpec, &outPathSize, &outPathHandle ); + if (err == noErr) + { + char* szPath = *outPathHandle; + wxString sClassicPath(szPath, wxConvLocal, outPathSize); + +#if defined(__DARWIN__) + // Classic Path --> Unix (OSX) Path + CFURLRef finalURL = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + wxMacCFStringHolder(sClassicPath, wxLocale::GetSystemEncoding()), + kCFURLHFSPathStyle, + false ); //false == not a directory + + //clean up memory from the classic path handle + DisposeHandle( outPathHandle ); + + if (finalURL) + { + CFStringRef cfsUnixPath = CFURLCopyFileSystemPath(finalURL, kCFURLPOSIXPathStyle); + CFRelease(finalURL); + + // PHEW! Success! + if (cfsUnixPath) + return wxMacCFStringHolder(cfsUnixPath).AsString(wxLocale::GetSystemEncoding()); + } +#else //classic HFS path acceptable + return sClassicPath; +#endif + } + else + { + wxLogMimeDebug(wxT("FSpGetFullPath failed."), (OSStatus)err); + } } - else - return FALSE; + + return wxEmptyString; +} +#endif //!DARWIN + +bool wxFileTypeImpl::GetDescription(wxString *desc) const +{ + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + ICMapEntry entry; + ICGetMapEntry( (ICInstance) m_manager->m_hIC, + (Handle) m_manager->m_hDatabase, m_lIndex, &entry ); + + *desc = wxMacMakeStringFromPascal( entry.entryName ); + + return true; +} + +bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) +{ + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + ICMapEntry entry; + ICGetMapEntry( (ICInstance) m_manager->m_hIC, + (Handle) m_manager->m_hDatabase, m_lIndex, &entry ); + + //entry has period in it + wxString sCurrentExtension = wxMacMakeStringFromPascal( entry.extension ); + extensions.Add( sCurrentExtension.Right( sCurrentExtension.Length() - 1 ) ); + + return true; +} + +bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const +{ + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + ICMapEntry entry; + ICGetMapEntry( (ICInstance) m_manager->m_hIC, + (Handle) m_manager->m_hDatabase, m_lIndex, &entry ); + + *mimeType = wxMacMakeStringFromPascal(entry.MIMEType); + + return true; } bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const { wxString s; - + if (GetMimeType(&s)) { mimeTypes.Clear(); mimeTypes.Add(s); - return TRUE; + + return true; } - else - return FALSE; + + return false; } -bool wxFileTypeImpl::GetIcon(wxIcon *icon, wxString *sCommand, int *iIndex) const +bool wxFileTypeImpl::GetIcon(wxIconLocation *WXUNUSED(icon)) const { + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + // no such file type or no value or incorrect icon entry - return FALSE; + return false; } -bool wxFileTypeImpl::GetDescription(wxString *desc) const +size_t wxFileTypeImpl::GetAllCommands(wxArrayString * verbs, + wxArrayString * commands, + const wxFileType::MessageParameters& params) const { - return FALSE; + wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); + + wxString sCommand; + size_t ulCount = 0; + + if (GetOpenCommand(&sCommand, params)) + { + verbs->Add(wxString(wxT("open"))); + commands->Add(sCommand); + ++ulCount; + } + + return ulCount; } -size_t -wxFileTypeImpl::GetAllCommands(wxArrayString * verbs, wxArrayString * commands, - const wxFileType::MessageParameters& params) const +void wxMimeTypesManagerImpl::Initialize(int mailcapStyles, const wxString& extraDir) { - wxFAIL_MSG( _T("TODO") ); - return 0; + wxASSERT_MSG(m_hIC == NULL, wxT("Already initialized wxMimeTypesManager!")); + + // some apps (non-wx) use the 'plst' resource instead +#if 0 + CFBundleRef cfbMain = CFBundleGetMainBundle(); + wxCFDictionary cfdInfo( CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN ); + wxString sLog; + cfdInfo.PrintOut(sLog); + wxLogDebug(sLog); +#endif + + // start Internet Config - log if there's an error + // the second param is the signature of the application, also known + // as resource ID 0. However, as per some recent discussions, we may not + // have a signature for this app, so a generic 'APPL' which is the executable + // type will work for now. + OSStatus status = ICStart( (ICInstance*)&m_hIC, 'APPL' ); + + if (status != noErr) + { + wxLogDebug(wxT("Could not initialize wxMimeTypesManager!")); + wxASSERT( false ); + m_hIC = NULL; + + return; + } + + ICAttr attr; + m_hDatabase = (void**) NewHandle(0); + status = ICFindPrefHandle( (ICInstance) m_hIC, kICMapping, &attr, (Handle) m_hDatabase ); + + //the database file can be corrupt (on OSX its + //~/Library/Preferences/com.apple.internetconfig.plist) + //- bail if it is + if (status != noErr) + { + ClearData(); + wxLogDebug(wxT("Corrupt MIME database!")); + return; + } + + //obtain the number of entries in the map + status = ICCountMapEntries( (ICInstance) m_hIC, (Handle) m_hDatabase, &m_lCount ); + wxASSERT( status == noErr ); + +#if 0 + //debug stuff + ICMapEntry entry; + long pos; + + for (long i = 1; i <= m_lCount; ++i) + { + OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry ); + + if (status == noErr) + { + wxString sCreator = wxMacMakeStringFromPascal(entry.creatorAppName); + wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); + wxString sMIMEType = wxMacMakeStringFromPascal(entry.MIMEType); + + wxFileTypeImpl impl; + impl.Init(this, pos); + + if (sMIMEType == wxT("text/html") && sCurrentExtension == wxT(".html")) + { + wxString cmd; + + impl.GetOpenCommand( &cmd, wxFileType::MessageParameters (wxT("http://www.google.com"))); + wxPrintf(wxT("APP: [%s]\n"), cmd.c_str()); + } + } + } +#endif } -void -wxMimeTypesManagerImpl::Initialize(int mailcapStyles, const wxString& extraDir) +void wxMimeTypesManagerImpl::ClearData() { - wxFAIL_MSG( _T("TODO") ); + if (m_hIC != NULL) + { + DisposeHandle( (Handle)m_hDatabase ); + + // this can return an error, but we don't really care that much about it + ICStop( (ICInstance)m_hIC ); + m_hIC = NULL; + } } -void -wxMimeTypesManagerImpl::ClearData() +// +// Q) Iterating through the map - why does it use if (err == noErr) instead of just asserting? +// A) Some intermediate indexes are bad while subsequent ones may be good. Its wierd, I know. +// + +// extension -> file type +wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& e) { - wxFAIL_MSG( _T("TODO") ); + wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); + + //low level functions - iterate through the database + ICMapEntry entry; + long pos; + + for (long i = 1; i <= m_lCount; ++i) + { + OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry ); + + if (status == noErr) + { + wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); + if ( sCurrentExtension.Right(sCurrentExtension.Length() - 1) == e ) // entry has period in it + { + wxFileType* pFileType = new wxFileType(); + pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos); + + return pFileType; + } + } + } + + return NULL; } -// extension -> file type -wxFileType * -wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& e) +// MIME type -> extension -> file type +wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) { - wxString ext = e ; - ext = ext.Lower() ; - if ( ext == "txt" ) + wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); + + ICMapEntry entry; + long pos; + + // low level functions - iterate through the database + for (long i = 1; i <= m_lCount; ++i) { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("text/text"); - fileType->m_impl->SetExt(ext); - return fileType; + OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry ); + wxASSERT_MSG( status == noErr, wxString::Format(wxT("Error: %d"), (int)status) ); + + if (status == noErr) + { + if ( wxMacMakeStringFromPascal(entry.MIMEType) == mimeType) + { + wxFileType* pFileType = new wxFileType(); + pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos); + + return pFileType; + } + } } - else if ( ext == "htm" || ext == "html" ) + + return NULL; +} + +size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes) +{ + wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); + + ICMapEntry entry; + long pos, lStartCount; + + // low level functions - iterate through the database + lStartCount = (long) mimetypes.GetCount(); + for (long i = 1; i <= m_lCount; ++i) { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("text/html"); - fileType->m_impl->SetExt(ext); - return fileType; + OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry ); + if ( status == noErr ) + mimetypes.Add( wxMacMakeStringFromPascal(entry.MIMEType) ); } - else if ( ext == "gif" ) + + return mimetypes.GetCount() - lStartCount; +} + +pascal OSStatus MoreProcGetProcessTypeSignature( + const ProcessSerialNumberPtr pPSN, + OSType *pProcessType, + OSType *pCreator) +{ + OSStatus anErr = noErr; + ProcessInfoRec infoRec; + ProcessSerialNumber localPSN; + + infoRec.processInfoLength = sizeof(ProcessInfoRec); + infoRec.processName = NULL; + infoRec.processAppSpec = NULL; + + if ( pPSN == NULL ) { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("image/gif"); - fileType->m_impl->SetExt(ext); - return fileType; + localPSN.highLongOfPSN = 0; + localPSN.lowLongOfPSN = kCurrentProcess; } - else if ( ext == "png" ) + else { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("image/png"); - fileType->m_impl->SetExt(ext); - return fileType; + localPSN = *pPSN; } - else if ( ext == "jpg" || ext == "jpeg" ) + + anErr = GetProcessInformation(&localPSN, &infoRec); + if (anErr == noErr) { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("image/jpeg"); - fileType->m_impl->SetExt(ext); - return fileType; + *pProcessType = infoRec.processType; + *pCreator = infoRec.processSignature; } - else if ( ext == "bmp" ) + + return anErr; +} + +// +// +// TODO: clean this up, its messy +// +// +// + +#include "wx/mac/corefoundation/cfstring.h" +#include "wx/intl.h" //wxLocale for wxCFString + +#define wxCF_RELEASE true +#define wxCF_RETAIN false + +// ---------------------------------------------------------------------------- +// wxCFDictionary +// ---------------------------------------------------------------------------- + +class wxCFDictionary +{ +public: + wxCFDictionary(CFTypeRef ref, bool bRetain = wxCF_RELEASE) { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("image/bmp"); - fileType->m_impl->SetExt(ext); - return fileType; + m_cfmdRef = (CFMutableDictionaryRef) ref; + if (bRetain == wxCF_RETAIN && ref) + CFRetain(ref); } - else if ( ext == "tif" || ext == "tiff" ) + + wxCFDictionary(CFIndex cfiSize = 0) { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("image/tiff"); - fileType->m_impl->SetExt(ext); - return fileType; + CFDictionaryKeyCallBacks kcbs; + CFDictionaryValueCallBacks vcbs; + BuildKeyCallbacks(&kcbs); + BuildValueCallbacks(&vcbs); + + m_cfmdRef = CFDictionaryCreateMutable( + kCFAllocatorDefault, cfiSize, &kcbs, &vcbs ); } - else if ( ext == "xpm" ) + + ~wxCFDictionary() + { Clear(); } + + void Clear() { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("image/xpm"); - fileType->m_impl->SetExt(ext); - return fileType; + if (m_cfmdRef) + CFRelease(m_cfmdRef); } - else if ( ext == "xbm" ) + + static const void* RetainProc(CFAllocatorRef, const void* v) + { return (const void*) CFRetain(v); } + + static void ReleaseProc(CFAllocatorRef, const void* v) + { CFRelease(v); } + + void MakeMutable(CFIndex cfiSize = 0) { - wxFileType *fileType = new wxFileType; - fileType->m_impl->SetFileType("image/xbm"); - fileType->m_impl->SetExt(ext); - return fileType; + CFDictionaryRef oldref = (CFDictionaryRef) m_cfmdRef; + + m_cfmdRef = CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, cfiSize, oldref ); + + CFRelease( oldref ); } - // unknown extension - return NULL; -} + void BuildKeyCallbacks(CFDictionaryKeyCallBacks* pCbs) + { + pCbs->version = 0; + pCbs->retain = RetainProc; + pCbs->release = ReleaseProc; + pCbs->copyDescription = NULL; + pCbs->equal = NULL; + pCbs->hash = NULL; + } -// MIME type -> extension -> file type -wxFileType * -wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) + void BuildValueCallbacks(CFDictionaryValueCallBacks* pCbs) + { + pCbs->version = 0; + pCbs->retain = RetainProc; + pCbs->release = ReleaseProc; + pCbs->copyDescription = NULL; + pCbs->equal = NULL; + } + + operator CFTypeRef () const + { return (CFTypeRef)m_cfmdRef; } + + CFDictionaryRef GetCFDictionary() const + { return (CFDictionaryRef)m_cfmdRef; } + + CFMutableDictionaryRef GetCFMutableDictionary() + { return (CFMutableDictionaryRef) m_cfmdRef; } + + CFTypeRef operator [] (CFTypeRef cftEntry) const + { + wxASSERT(IsValid()); + return (CFTypeRef) CFDictionaryGetValue((CFDictionaryRef)m_cfmdRef, cftEntry); + } + + CFIndex GetCount() const + { + wxASSERT(IsValid()); + return CFDictionaryGetCount((CFDictionaryRef)m_cfmdRef); + } + + void Add(CFTypeRef cftKey, CFTypeRef cftValue) + { + wxASSERT(IsValid()); + wxASSERT(Exists(cftKey) == false); + CFDictionaryAddValue(m_cfmdRef, cftKey, cftValue); + } + + void Remove(CFTypeRef cftKey) + { + wxASSERT(IsValid()); + wxASSERT(Exists(cftKey)); + CFDictionaryRemoveValue(m_cfmdRef, cftKey); + } + + void Set(CFTypeRef cftKey, CFTypeRef cftValue) + { + wxASSERT(IsValid()); + wxASSERT(Exists(cftKey)); + CFDictionarySetValue(m_cfmdRef, cftKey, cftValue); + } + + bool Exists(CFTypeRef cftKey) const + { + wxASSERT(IsValid()); + return CFDictionaryContainsKey((CFDictionaryRef)m_cfmdRef, cftKey); + } + + bool IsOk() const + { return m_cfmdRef != NULL; } + + bool IsValid() const + { return IsOk() && CFGetTypeID((CFTypeRef)m_cfmdRef) == CFDictionaryGetTypeID(); } + + void PrintOut(wxString& sMessage) + { + PrintOutDictionary(sMessage, m_cfmdRef); + } + + static void PrintOutDictionary(wxString& sMessage, CFDictionaryRef cfdRef) + { + CFIndex cfiCount = CFDictionaryGetCount(cfdRef); + CFTypeRef* pKeys = new CFTypeRef[cfiCount]; + CFTypeRef* pValues = new CFTypeRef[cfiCount]; + + CFDictionaryGetKeysAndValues(cfdRef, pKeys, pValues); + + for (CFIndex i = 0; i < cfiCount; ++i) + { + wxString sKey = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID(pKeys[i]))).AsString(); + wxString sValue = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID(pValues[i]))).AsString(); + + sMessage << + wxString::Format(wxT("[{#%d} Key : %s]"), (int) i, + sKey.c_str()); + + PrintOutType(sMessage, sKey, pKeys[i]); + + sMessage << + wxString::Format(wxT("\n\t[Value : %s]"), + sValue.c_str()); + + PrintOutType(sMessage, sValue, pValues[i]); + + sMessage << wxT("\n"); + } + + delete [] pKeys; + delete [] pValues; + } + + static void PrintOutArray(wxString& sMessage, CFArrayRef cfaRef) + { + for (CFIndex i = 0; i < CFArrayGetCount(cfaRef); ++i) + { + wxString sValue = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID( + CFArrayGetValueAtIndex(cfaRef, i) + ))).AsString(); + + sMessage << + wxString::Format(wxT("\t\t[{#%d} ArrayValue : %s]\n"), (int) i, + sValue.c_str()); + + PrintOutType(sMessage, sValue, CFArrayGetValueAtIndex(cfaRef, i)); + } + } + + static void PrintOutType(wxString& sMessage, const wxString& sValue, CFTypeRef cfRef) + { + sMessage << wxT(" {"); + + if (sValue == wxT("CFString")) + { + sMessage << wxMacCFStringHolder((CFStringRef)cfRef, false).AsString(); + } + else if (sValue == wxT("CFNumber")) + { + int nOut; + CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, &nOut); + sMessage << nOut; + } + else if (sValue == wxT("CFDictionary")) + { + PrintOutDictionary(sMessage, (CFDictionaryRef)cfRef); + } + else if (sValue == wxT("CFArray")) + { + PrintOutArray(sMessage, (CFArrayRef)cfRef); + } + else if (sValue == wxT("CFBoolean")) + { + sMessage << (cfRef == kCFBooleanTrue ? wxT("true") : wxT("false")); + } + else if (sValue == wxT("CFURL")) + { + sMessage << wxMacCFStringHolder(CFURLCopyPath((CFURLRef) cfRef)).AsString(); + } + else + { + sMessage << wxT("*****UNKNOWN TYPE******"); + } + + sMessage << wxT("} "); + } + +#if wxUSE_MIMETYPE + void MakeValidXML(); +#endif + + CFTypeRef WriteAsXML() + { + return CFPropertyListCreateXMLData(kCFAllocatorDefault, m_cfmdRef); + } + + bool ReadAsXML(CFTypeRef cfData, wxString* pErrorMsg = NULL) + { + Clear(); + CFStringRef cfsError=NULL; + m_cfmdRef = (CFMutableDictionaryRef) CFPropertyListCreateFromXMLData( + kCFAllocatorDefault, + (CFDataRef)cfData, + kCFPropertyListMutableContainersAndLeaves, + &cfsError ); + if (cfsError) + { + if (pErrorMsg) + *pErrorMsg = wxMacCFStringHolder(cfsError).AsString(); + else + CFRelease(cfsError); + } + + return m_cfmdRef != NULL; + } + +private: + CFMutableDictionaryRef m_cfmdRef; +}; + +// ---------------------------------------------------------------------------- +// wxCFArray +// ---------------------------------------------------------------------------- + +class wxCFArray { - return NULL; -} +public: + wxCFArray(CFTypeRef ref, bool bRetain = wxCF_RELEASE) + { + m_cfmaRef = (CFMutableArrayRef)ref; + if (bRetain == wxCF_RETAIN && ref) + CFRetain(ref); + } -size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes) + wxCFArray(CFIndex cfiSize = 0) : m_cfmaRef(NULL) + { Create(cfiSize); } + + ~wxCFArray() + { Clear(); } + + void MakeMutable(CFIndex cfiSize = 0) + { + wxASSERT(IsValid()); + + CFMutableArrayRef oldref = m_cfmaRef; + m_cfmaRef = CFArrayCreateMutableCopy( + kCFAllocatorDefault, + cfiSize, + (CFArrayRef)oldref); + CFRelease(oldref); + } + + void BuildCallbacks(CFArrayCallBacks* pCbs) + { + pCbs->version = 0; + pCbs->retain = RetainProc; + pCbs->release = ReleaseProc; + pCbs->copyDescription = NULL; + pCbs->equal = NULL; + } + + void Create(CFIndex cfiSize = 0) + { + Clear(); + CFArrayCallBacks cb; + BuildCallbacks(&cb); + + m_cfmaRef = CFArrayCreateMutable(kCFAllocatorDefault, cfiSize, &cb); + } + + void Clear() + { if (m_cfmaRef) CFRelease(m_cfmaRef); } + + static const void* RetainProc(CFAllocatorRef, const void* v) + { return (const void*) CFRetain(v); } + + static void ReleaseProc(CFAllocatorRef, const void* v) + { CFRelease(v); } + + operator CFTypeRef () const + { return (CFTypeRef)m_cfmaRef; } + + CFArrayRef GetCFArray() const + { return (CFArrayRef)m_cfmaRef; } + + CFMutableArrayRef GetCFMutableArray() + { return (CFMutableArrayRef) m_cfmaRef; } + + CFTypeRef operator [] (CFIndex cfiIndex) const + { + wxASSERT(IsValid()); + return (CFTypeRef) CFArrayGetValueAtIndex((CFArrayRef)m_cfmaRef, cfiIndex); + } + + CFIndex GetCount() + { + wxASSERT(IsValid()); + return CFArrayGetCount((CFArrayRef)m_cfmaRef); + } + + void Add(CFTypeRef cftValue) + { + wxASSERT(IsValid()); + CFArrayAppendValue(m_cfmaRef, cftValue); + } + + void Remove(CFIndex cfiIndex) + { + wxASSERT(IsValid()); + wxASSERT(cfiIndex < GetCount()); + CFArrayRemoveValueAtIndex(m_cfmaRef, cfiIndex); + } + + void Set(CFIndex cfiIndex, CFTypeRef cftValue) + { + wxASSERT(IsValid()); + wxASSERT(cfiIndex < GetCount()); + CFArraySetValueAtIndex(m_cfmaRef, cfiIndex, cftValue); + } + + bool IsOk() const + { return m_cfmaRef != NULL; } + + bool IsValid() const + { + return IsOk() && CFGetTypeID((CFTypeRef)m_cfmaRef) == CFArrayGetTypeID(); + } + +#if wxUSE_MIMETYPE + void MakeValidXML(); +#endif + +private: + CFMutableArrayRef m_cfmaRef; +}; + +// ---------------------------------------------------------------------------- +// wxCFString +// ---------------------------------------------------------------------------- + +class wxCFString +{ +public: + wxCFString(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_Holder((CFStringRef)ref, bRetain == wxCF_RELEASE) + {} + + wxCFString(const wxChar* szString) : m_Holder(wxString(szString), wxLocale::GetSystemEncoding()) + {} + + wxCFString(const wxString& sString) : m_Holder(sString, wxLocale::GetSystemEncoding()) + {} + + virtual ~wxCFString() {} + + operator CFTypeRef() const + { return (CFTypeRef) ((CFStringRef) m_Holder); } + + bool IsOk() + { return ((CFTypeRef)(*this)) != NULL; } + + wxString BuildWXString() + { return m_Holder.AsString(); } + +private: + wxMacCFStringHolder m_Holder; +}; + +// ---------------------------------------------------------------------------- +// wxCFNumber +// ---------------------------------------------------------------------------- + +class wxCFNumber +{ +public: + wxCFNumber(int nValue) + { + m_cfnRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &nValue); + } + + wxCFNumber(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_cfnRef((CFNumberRef)ref) + { + if (bRetain == wxCF_RETAIN && ref) + CFRetain(ref); + } + + virtual ~wxCFNumber() + { + if (m_cfnRef) + CFRelease(m_cfnRef); + } + + operator CFTypeRef() const + { return (CFTypeRef) m_cfnRef; } + + int GetValue() + { + int nOut; + CFNumberGetValue( m_cfnRef, kCFNumberIntType, &nOut ); + + return nOut; + } + + bool IsOk() + { return m_cfnRef != NULL; } + +private: + CFNumberRef m_cfnRef; +}; + +// ---------------------------------------------------------------------------- +// wxCFURL +// ---------------------------------------------------------------------------- + +class wxCFURL +{ +public: + wxCFURL(CFTypeRef ref = NULL, bool bRetain = wxCF_RELEASE) : m_cfurlRef((CFURLRef)ref) + { + if (bRetain == wxCF_RETAIN && ref) + CFRetain(ref); + } + + wxCFURL(const wxCFString& URLString, CFTypeRef BaseURL = NULL) + { + Create(URLString, BaseURL); + } + + void Create(const wxCFString& URLString, CFTypeRef BaseURL = NULL) + { + m_cfurlRef = CFURLCreateWithString( + kCFAllocatorDefault, + (CFStringRef)(CFTypeRef)URLString, + (CFURLRef) BaseURL); + } + + virtual ~wxCFURL() + { + if (m_cfurlRef) + CFRelease(m_cfurlRef); + } + + wxString BuildWXString() + { + return wxCFString(CFURLCopyPath(m_cfurlRef)).BuildWXString(); + } + + operator CFTypeRef() const + { return (CFTypeRef)m_cfurlRef; } + + bool IsOk() + { return m_cfurlRef != NULL; } + +private: + CFURLRef m_cfurlRef; +}; + +// ---------------------------------------------------------------------------- +// wxCFData +// ---------------------------------------------------------------------------- + +#define wxCFDATA_RELEASEBUFFER 1 +#define wxCFDATA_RETAINBUFFER 0 + +class wxCFData +{ +public: + wxCFData(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_cfdaRef((CFDataRef)ref) + { + if (bRetain == wxCF_RETAIN && ref) + CFRetain(ref); + } + + wxCFData(const UInt8* pBytes, CFIndex len, bool bKeep = wxCFDATA_RELEASEBUFFER) + { + if (bKeep == wxCFDATA_RELEASEBUFFER) + { + m_cfdaRef = CFDataCreateWithBytesNoCopy + (kCFAllocatorDefault, pBytes, len, kCFAllocatorDefault); + } + else + { + m_cfdaRef = CFDataCreate(kCFAllocatorDefault, pBytes, len); + } + } + + virtual ~wxCFData() + { + if (m_cfdaRef) + CFRelease(m_cfdaRef); + } + + const UInt8* GetValue() + { return CFDataGetBytePtr(m_cfdaRef); } + + CFIndex GetCount() + { return CFDataGetLength(m_cfdaRef); } + + operator CFTypeRef() const + { return (CFTypeRef)m_cfdaRef; } + + bool IsOk() + { return m_cfdaRef != NULL; } + +private: + CFDataRef m_cfdaRef; +}; + +void wxCFDictionary::MakeValidXML() { - wxFAIL_MSG( _T("TODO") ); // VZ: don't know anything about this for Mac + CFIndex cfiCount = GetCount(); + CFTypeRef* pKeys = new CFTypeRef[cfiCount]; + CFTypeRef* pValues = new CFTypeRef[cfiCount]; + + CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues); + + // for plist xml format, all dictionary keys must be cfstrings and + // no values in the dictionary or subkeys/values can be NULL; + // additionally, CFURLs are not allowed + for (CFIndex i = 0; i < cfiCount; ++i) + { + // must be an array, dictionary, string, bool, or int and cannot be null + // and dictionaries can only contain cfstring keys + CFTypeRef cfRef = pValues[i]; + if (!pKeys[i] || + CFGetTypeID(pKeys[i]) != CFStringGetTypeID() || + !cfRef) + { + Remove(pKeys[i]); + --i; + --cfiCount; + delete [] pKeys; + delete [] pValues; + pKeys = new CFTypeRef[cfiCount]; + pValues = new CFTypeRef[cfiCount]; + CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues); + } + else if (CFGetTypeID(cfRef) == CFArrayGetTypeID()) + { + CFRetain(cfRef); + wxCFArray cfaCurrent(cfRef); + cfaCurrent.MakeMutable(); + cfaCurrent.MakeValidXML(); + Set(pKeys[i], cfaCurrent); + } + else if (CFGetTypeID(cfRef) == CFDictionaryGetTypeID()) + { + CFRetain(cfRef); + wxCFDictionary cfdCurrent(cfRef); + cfdCurrent.MakeMutable(); + cfdCurrent.MakeValidXML(); + Set(pKeys[i], cfdCurrent); + } + else if ( CFGetTypeID(cfRef) != CFStringGetTypeID() && + CFGetTypeID(cfRef) != CFNumberGetTypeID() && + CFGetTypeID(cfRef) != CFBooleanGetTypeID() ) + { + Remove(pKeys[i]); + --i; + --cfiCount; + delete [] pKeys; + delete [] pValues; + pKeys = new CFTypeRef[cfiCount]; + pValues = new CFTypeRef[cfiCount]; + CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues); + } + } - return 0; + delete [] pValues; + delete [] pKeys; } -wxFileType * -wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo) +void wxCFArray::MakeValidXML() +{ + for (CFIndex i = 0; i < GetCount(); ++i) + { + //must be an array, dictionary, string, bool, or int and cannot be null + //and dictionaries can only contain cfstring keys + CFTypeRef cfRef = (*this)[i]; + if (!cfRef) + { + Remove(i); + --i; + } + else if (CFGetTypeID(cfRef) == CFArrayGetTypeID()) + { + CFRetain(cfRef); + wxCFArray cfaCurrent(cfRef); + cfaCurrent.MakeMutable(); + cfaCurrent.MakeValidXML(); + Set(i, cfaCurrent); + } + else if (CFGetTypeID(cfRef) == CFDictionaryGetTypeID()) + { + CFRetain(cfRef); + wxCFDictionary cfdCurrent(cfRef); + cfdCurrent.MakeMutable(); + cfdCurrent.MakeValidXML(); + Set(i, cfdCurrent); + } + else if ( CFGetTypeID(cfRef) != CFStringGetTypeID() && + CFGetTypeID(cfRef) != CFNumberGetTypeID() && + CFGetTypeID(cfRef) != CFBooleanGetTypeID() ) + { + Remove(i); + --i; + } + } +} + +// +// +// +// END TODO +// +// +// + +wxFileType* wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo) { - wxFAIL_MSG( _T("TODO") ); + bool bInfoSuccess = false; + + const wxArrayString& asExtensions = ftInfo.GetExtensions(); + size_t dwFoundIndex = 0; + if (!asExtensions.GetCount()) + { + wxLogDebug(wxT("Must have extension to associate with")); + } + + // Find and write to Info.plist in main bundle (note that some other + // apps have theirs named differently, i.e. IE's is named Info-macos.plist + // some apps (non-wx) use the 'plst' resource instead + CFBundleRef cfbMain = CFBundleGetMainBundle(); + if (cfbMain) + { + UInt32 dwBundleType, dwBundleCreator; + CFBundleGetPackageInfo(cfbMain, &dwBundleType, &dwBundleCreator); + + // if launching terminal non-app, version will be 'BNDL' (generic bundle, maybe in other cases too), + // which will give us the incorrect info.plist path + // otherwise it will be 'APPL', or in the case of a framework, 'FMWK' + if (dwBundleType == 'APPL') + { + wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyBundleURL(cfbMain)); +// wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain)); + wxString sInfoPath; +// sInfoPath << wxT("file://"); + sInfoPath << cfurlBundleLoc.BuildWXString(); + sInfoPath << wxT("Contents/Info.plist"); + +// wxCFDictionary cfdInfo( CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN ); + wxCFDictionary cfdInfo; + bool bInfoOpenSuccess = false; + wxFile indictfile; + if (indictfile.Open(sInfoPath, wxFile::read)) + { + CFIndex cfiBufLen = (CFIndex) indictfile.Length(); + const UInt8* pBuffer = new UInt8[cfiBufLen]; + indictfile.Read((void*)pBuffer, cfiBufLen); + wxCFData cfdaInDict(pBuffer, cfiBufLen); + wxString sError; + bInfoOpenSuccess = cfdInfo.ReadAsXML(cfdaInDict, &sError); + if (!bInfoOpenSuccess) + wxLogDebug(sError); + indictfile.Close(); + } + + if (bInfoOpenSuccess) + { + cfdInfo.MakeMutable( cfdInfo.GetCount() + 1 ); + + wxCFArray cfaDocTypes( cfdInfo[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN ); + + bool bAddDocTypesArrayToDictionary = !cfaDocTypes.IsOk(); + if (bAddDocTypesArrayToDictionary) + cfaDocTypes.Create(); + else + cfaDocTypes.MakeMutable( cfaDocTypes.GetCount() + 1 ); + + bool bEntryFound = false; + + // search for duplicates + CFIndex i; + for (i = 0; i < cfaDocTypes.GetCount(); ++i) + { + wxCFDictionary cfdDocTypeEntry( cfaDocTypes[i], wxCF_RETAIN ); + + // A lot of apps don't support MIME types for some reason + // so we go by extensions only + wxCFArray cfaExtensions( cfdDocTypeEntry[ wxCFString(wxT("CFBundleTypeExtensions")) ], + wxCF_RETAIN ); + + if (!cfaExtensions.IsOk()) + continue; + + for (CFIndex iExt = 0; iExt < cfaExtensions.GetCount(); ++iExt) + { + for (size_t iWXExt = 0; iWXExt < asExtensions.GetCount(); ++iWXExt) + { + if (asExtensions[iWXExt] == + wxCFString(cfaExtensions[iExt], wxCF_RETAIN).BuildWXString()) + { + bEntryFound = true; + dwFoundIndex = iWXExt; + + break; + } + } //end of wxstring array + + if (bEntryFound) + break; + } //end for cf array + + if (bEntryFound) + break; + } //end for doctypes + + wxCFDictionary cfdNewEntry; + + if (!ftInfo.GetDescription().empty()) + { + cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeName")), + wxCFString(ftInfo.GetDescription()) ); + } + + if (!ftInfo.GetIconFile().empty()) + { + cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeIconFile")), + wxCFString(ftInfo.GetIconFile()) ); + } + + wxCFArray cfaOSTypes; + wxCFArray cfaExtensions; + wxCFArray cfaMimeTypes; + + //OSTypes is a cfarray of four-char-codes - '****' for unrestricted + cfaOSTypes.Add( wxCFString(wxT("****")) ); + cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeOSTypes")), cfaOSTypes ); + + //'*' for unrestricted + if (ftInfo.GetExtensionsCount() != 0) + { + for (size_t iExtension = 0; iExtension < (size_t)ftInfo.GetExtensionsCount(); ++iExtension) + { + cfaExtensions.Add( wxCFString( asExtensions[iExtension] ) ); + } + + cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeExtensions")), cfaExtensions ); + } + + if (!ftInfo.GetMimeType().empty()) + { + cfaMimeTypes.Add( wxCFString(ftInfo.GetMimeType()) ); + cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeMIMETypes")), cfaMimeTypes ); + } + + // Editor - can perform all actions + // Viewer - all actions except manipulation/saving + // None - can perform no actions + cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeRole")), wxCFString(wxT("Editor")) ); + + // Is application bundled? + cfdNewEntry.Add( wxCFString(wxT("LSTypeIsPackage")), kCFBooleanTrue ); + + if (bEntryFound) + cfaDocTypes.Set(i, cfdNewEntry); + else + cfaDocTypes.Add(cfdNewEntry); + + // set the doc types array in the muted dictionary + if (bAddDocTypesArrayToDictionary) + cfdInfo.Add(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes); + else + cfdInfo.Set(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes); + + cfdInfo.MakeValidXML(); + + wxFile outdictfile; + if (outdictfile.Open(sInfoPath, wxFile::write)) + { + wxCFData cfdaInfo(cfdInfo.WriteAsXML()); + if (cfdaInfo.IsOk()) + { + if (outdictfile.Write(cfdaInfo.GetValue(), cfdaInfo.GetCount()) != + (wxFileOffset)cfdaInfo.GetCount()) + { + wxLogDebug(wxT("error in writing to file")); + } + else + { + bInfoSuccess = true; + +//#if defined(__DARWIN__) +// //force launch services to update its database for the finder +// OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true); +// if (status != noErr) +// { +// wxLogDebug(wxT("LSRegisterURL Failed.")); +// } +//#endif + } + outdictfile.Close(); + } + else + { + outdictfile.Close(); + wxLogDebug(wxT("Could not read in new dictionary")); + } + } + else + { + wxLogDebug(wxString(wxT("Could not open [")) + + sInfoPath + wxT("] for writing.")); + } + } + else + { + wxLogDebug(wxT("No info dictionary in main bundle")); + } + } + else + { + wxLogDebug(wxT("Can only call associate from bundled app within XXX.app")); + } + } + else + { + wxLogDebug(wxT("No main bundle")); + } + +#if defined(__DARWIN__) + if (!bInfoSuccess) + return NULL; +#endif + + // on mac you have to embed it into the mac's file reference resource ('FREF' I believe) + // or, alternately, you could just add an entry to m_hDatabase, but you'd need to get + // the app's signature somehow... + + OSType processType, creator; + OSStatus status = MoreProcGetProcessTypeSignature(NULL, &processType, &creator); + + if (status == noErr) + { + Str255 psCreatorName; + FSSpec dummySpec; + status = FindApplication(creator, false, psCreatorName, &dummySpec); + + if (status == noErr) + { + //get the file type if it exists - + //if it really does then modify the database then save it, + //otherwise we need to create a whole new entry + wxFileType* pFileType = GetFileTypeFromExtension(asExtensions[dwFoundIndex]); + if (pFileType) + { + ICMapEntry entry; + ICGetMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, + pFileType->m_impl->m_lIndex, &entry ); + + memcpy(entry.creatorAppName, psCreatorName, sizeof(Str255)); + entry.fileCreator = creator; + + status = ICSetMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, + pFileType->m_impl->m_lIndex, &entry ); + + //success + if (status == noErr) + { + return pFileType; + + //kICAttrNoChange means we don't care about attributes such as + //locking in the database +// status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping, +// kICAttrNoChange, (Handle) m_hDatabase); +// if (status == noErr) +// return pFileType; +// else +// { +// wxLogDebug(wxString::Format(wxT("%i - %s"), (int)status, wxT("ICSetPrefHandle failed."))); +// } + } + else + { + wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetMapEntry failed."))); + } + + // failure - cleanup + delete pFileType; + } + else + { + // TODO: Maybe force all 3 of these to be non-empty? + Str255 psExtension, psMimeType, psDescription; + + wxMacStringToPascal(wxString(wxT(".")) + ftInfo.GetExtensions()[0], psExtension); + wxMacStringToPascal(ftInfo.GetMimeType(), psMimeType); + wxMacStringToPascal(ftInfo.GetDescription(), psDescription); + + Str255 psPostCreatorName; + wxMacStringToPascal(wxT(""), psPostCreatorName); + + //add the entry to the database + ICMapEntry entry; + entry.totalLength = sizeof(ICMapEntry); + entry.fixedLength = kICMapFixedLength; + entry.version = 0; + entry.fileType = 0; //TODO: File type? + entry.fileCreator = creator; + entry.postCreator = 0; + entry.flags = kICMapDataForkBit; //TODO: Maybe resource is valid by default too? + PLstrcpy( entry.extension , psExtension ) ; + memcpy(entry.creatorAppName, psCreatorName, sizeof(Str255)); + memcpy(entry.postAppName, psPostCreatorName, sizeof(Str255)); + memcpy(entry.MIMEType, psMimeType, sizeof(Str255)); + memcpy(entry.entryName, psDescription, sizeof(Str255)); + + status = ICAddMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, &entry); + if (status == noErr) + { + return GetFileTypeFromExtension(ftInfo.GetMimeType()); + +// kICAttrNoChange means we don't care about attributes such as +// locking in the database +// status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping, +// kICAttrNoChange, (Handle) m_hDatabase); + + // return the entry in the database if successful +// if (status == noErr) +// return GetFileTypeFromExtension(ftInfo.GetMimeType()); +// else +// { +// wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed."))); + // } + } + else + { + wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICAppMapEntry failed."))); + } + } + } // end if FindApplcation was successful + else + { + wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("FindApplication failed."))); + } + } // end if it could obtain app's signature + else + { + wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("GetProcessSignature failed."))); + } return NULL; } bool -wxMimeTypesManagerImpl::Unassociate(wxFileType *ft) +wxMimeTypesManagerImpl::Unassociate(wxFileType *pFileType) { - return FALSE; + wxASSERT(pFileType); + bool bInfoSuccess = false; + + wxArrayString asExtensions; + pFileType->GetExtensions(asExtensions); + + if (!asExtensions.GetCount()) + { + wxLogDebug(wxT("Must have extension to disassociate")); + return false; + } + + // Find and write to Info.plist in main bundle (note that some other + // apps have theirs named differently, i.e. IE's is named Info-macos.plist + // some apps (non-wx) use the 'plst' resource instead + CFBundleRef cfbMain = CFBundleGetMainBundle(); + if (cfbMain) + { + UInt32 dwBundleType, dwBundleCreator; + CFBundleGetPackageInfo(cfbMain, &dwBundleType, &dwBundleCreator); + + // if launching terminal non-app, version will be 'BNDL' (generic bundle, maybe in other cases too), + // which will give us the incorrect info.plist path + // otherwise it will be 'APPL', or in the case of a framework, 'FMWK' + if (dwBundleType == 'APPL') + { + + wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyBundleURL(cfbMain)); +// wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain)); + wxString sInfoPath; +// sInfoPath << wxT("file://"); + sInfoPath << cfurlBundleLoc.BuildWXString(); + sInfoPath << wxT("Contents/Info.plist"); + +// wxCFDictionary cfdInfo( (CFTypeRef) CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN ); + wxCFDictionary cfdInfo; + bool bInfoOpenSuccess = false; + wxFile indictfile; + if (indictfile.Open(sInfoPath, wxFile::read)) + { + CFIndex cfiBufLen = (CFIndex) indictfile.Length(); + const UInt8* pBuffer = new UInt8[cfiBufLen]; + indictfile.Read((void*)pBuffer, cfiBufLen); + wxCFData cfdaInDict(pBuffer, cfiBufLen); + wxString sError; + bInfoOpenSuccess = cfdInfo.ReadAsXML(cfdaInDict, &sError); + if (!bInfoOpenSuccess) + wxLogDebug(sError); + indictfile.Close(); + } + + if (bInfoOpenSuccess) + { + cfdInfo.MakeMutable( cfdInfo.GetCount() + 1 ); + + wxCFArray cfaDocTypes( cfdInfo[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN ); + + if (cfaDocTypes.IsOk()) + { + bool bEntryFound = false; + + //search for duplicate + CFIndex i; + for (i = 0; i < cfaDocTypes.GetCount(); ++i) + { + wxCFDictionary cfdDocTypeEntry( cfaDocTypes[i], wxCF_RETAIN ); + + //A lot of apps dont do to mime types for some reason + //so we go by extensions only + wxCFArray cfaExtensions( cfdDocTypeEntry[ wxCFString(wxT("CFBundleTypeExtensions")) ], + wxCF_RETAIN ); + + if (!cfaExtensions.IsOk()) + continue; + + for (CFIndex iExt = 0; iExt < cfaExtensions.GetCount(); ++iExt) + { + for (size_t iWXExt = 0; iWXExt < asExtensions.GetCount(); ++iWXExt) + { + if (asExtensions[iWXExt] == + wxCFString(cfaExtensions[iExt], wxCF_RETAIN).BuildWXString()) + { + bEntryFound = true; + cfaDocTypes.Remove(i); + cfdInfo.Set( wxCFString(wxT("CFBundleDocumentTypes")) , cfaDocTypes ); + break; + } + } //end of wxstring array + + if (bEntryFound) + break; + } //end for cf array + + if (bEntryFound) + break; + }//end for doctypes + + if (bEntryFound) + { + cfdInfo.MakeValidXML(); + + wxFile outdictfile; + if (outdictfile.Open(sInfoPath, wxFile::write)) + { + wxCFData cfdaInfo(cfdInfo.WriteAsXML()); + if (cfdaInfo.IsOk()) + { + if (outdictfile.Write(cfdaInfo.GetValue(), cfdaInfo.GetCount()) != + (wxFileOffset)cfdaInfo.GetCount()) + { + wxLogDebug(wxT("error in writing to file")); + } + else + { + bInfoSuccess = true; + +//#if defined(__DARWIN__) +// //force launch services to update its database for the finder +// OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true); +// if (status != noErr) +// { +// wxLogDebug(wxT("LSRegisterURL Failed.")); +// } +//#endif + } + outdictfile.Close(); + } + else + { + outdictfile.Close(); + wxLogDebug(wxT("Could not read in new dictionary")); + } + } + else + { + wxLogDebug( + wxString(wxT("Could not open [")) + + sInfoPath + wxT("] for writing.")); + } + } + else + { + wxLogDebug(wxT("Entry not found to remove")); + + wxString sPrintOut; + wxCFDictionary::PrintOutArray(sPrintOut, (CFArrayRef)(CFTypeRef)cfaDocTypes); + wxLogDebug(sPrintOut); + + for (size_t i = 0; i < asExtensions.GetCount(); ++i) + wxLogDebug(asExtensions[i]); + } + } + else + { + wxLogDebug(wxT("No doc types array found")); + wxString sPrintOut; cfdInfo.PrintOut(sPrintOut); wxLogDebug(sPrintOut); + } + } + else + { + wxLogDebug(wxT("No info dictionary in main bundle")); + } + } + else + { + wxLogDebug(wxT("Can only call associate from bundled app within XXX.app")); + } + } + else + { + wxLogDebug(wxT("No main bundle")); + } + +#if defined(__DARWIN__) + if (!bInfoSuccess) + return false; +#endif + + // this should be as easy as removing the entry from the database + // and then saving the database + OSStatus status = ICDeleteMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, + pFileType->m_impl->m_lIndex); + + if (status == noErr) + { + return true; + + //kICAttrNoChange means we don't care about attributes such as + //locking in the database +// status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping, +// kICAttrNoChange, (Handle) m_hDatabase); + +// if (status == noErr) +// { +// return true; +// } +// else +// { +// wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed."))); +// } + } + else + { + wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICDeleteMapEntry failed."))); + } + + return false; } +#if 0 + CFWriteStreamRef cfwsInfo = CFWriteStreamCreateWithFile( + kCFAllocatorDefault, + (CFURLRef) (CFTypeRef)cfurlInfoLoc ); + +// CFShow(cfdInfo); + if (cfwsInfo) + { + Boolean bOpened = CFWriteStreamOpen(cfwsInfo); + if (bOpened) + { + CFStringRef cfsError; + CFIndex cfiWritten = CFPropertyListWriteToStream((CFPropertyListRef)(CFTypeRef)cfdInfo, + cfwsInfo, + kCFPropertyListXMLFormat_v1_0, //100 + &cfsError); + if (cfsError && cfiWritten == 0) + { + wxLogDebug(wxCFString(cfsError).BuildWXString()); + wxString sMessage; + cfdInfo.PrintOut(sMessage); + wxLogDebug(sMessage); + } + else + { + bInfoSuccess = true; +//#if defined(__DARWIN__) +// //force launch services to update its database for the finder +// OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true); +// if (status != noErr) +// { +// wxLogDebug(wxT("LSRegisterURL Failed.")); +// } +//#endif + } + + CFWriteStreamClose(cfwsInfo); +#endif + +#endif //wxUSE_MIMETYPE