//************************************************************************************** // Filename: QuickAccessCM.cp // Part of Contextual Menu Workshop by Abracode Inc. // http://free.abracode.com/cmworkshop/ // Copyright © 2002-2003 Abracode, Inc. All rights reserved. // // Description: QuickAccess launcher, Move To, Copy To, Make Alias In // //************************************************************************************** #include "CFAbstractCMPlugin.h" #include "CMUtils.h" #include "MoreFilesExtras.h" #include "CFObjDel.h" #include "StAEDesc.h" #include "AEAliasList.h" #include "CFAliasAndNameArray.h" #include "StAllocThrowDisable.h" #include "ACFMutableString.h" #include "ACFNumber.h" #include "DebugSettings.h" OSStatus FSRefCheckFileOrFolder(const FSRef *inRef, void *ioData); OSStatus FSRefProcessFileOrFolder(const FSRef *inRef, void *ioData); void ReadPreferences(CMPluginImplData &ioData); void SavePreferences(CMPluginImplData &ioData); void AddItemToList(CMPluginImplData *ioData, const FSRef *inRef); Boolean PopulateItemsMenu(AEDescList* ioCommands, const CMPluginImplData &inData, Boolean addSeparator ); OSStatus FSRefObjectExists(const FSRef *inRef, void *ioData); OSStatus FSRefDeleteObject(const FSRef *inRef, void *ioData); Boolean CheckForExistingItemsInFolder(const AbstractCMPluginType *theThis, const AEDescList *fileList, FSRef &inFolderRef); Boolean AskUserIfOKToDelete(const AbstractCMPluginType *theThis); void TellUserWeFailedToDelete(const AbstractCMPluginType *theThis); Boolean DisplayWarning( CFStringRef inWarning, CFStringRef inOKString, CFStringRef inCancelStr ); struct ExistingObjects { FSRef *destFolderRef;//filled by caller AEAliasList existingObjList;//filled inside }; // --------------------------------------------------------------------------- // CMPluginExamineContext // --------------------------------------------------------------------------- // Have a look at the selection and decides whether to display any commands OSStatus CMPluginExamineContext( void *thisInstance, const AEDesc *inContext, AEDescList *outCommandPairs ) { TRACE_STR( "\pQuickAccessCM->CMPluginExamineContext" ); AbstractCMPluginType *theThis = (AbstractCMPluginType *)thisInstance; if(inContext == NULL) return errAENotAEDesc; if(inContext->descriptorType == typeNull) return errAENotAEDesc; OSErr err = noErr; try { theThis->implData.qaItems = NULL; theThis->implData.mtItems = NULL; theThis->implData.ctItems = NULL; theThis->implData.atItems = NULL; theThis->implData.currContext = kCCNothing; //kCCQuickAccess, kCCMoveTo, kCCCopyTo, kCCAliasTo theThis->implData.checkFlags = 0; theThis->implData.qaActive = true; theThis->implData.mtActive = true; theThis->implData.ctActive = true; theThis->implData.atActive = true; theThis->implData.mtShowDest = false; theThis->implData.ctShowDest = false; theThis->implData.qaUseHierMenus = false; theThis->implData.mtUseHierMenus = false; theThis->implData.ctUseHierMenus = false; theThis->implData.atUseHierMenus = false; ::Gestalt(gestaltSystemVersion, &(theThis->implData.sysVersion)); ReadPreferences(theThis->implData); UInt32 theFlags = kProcBreakOnFirst; Boolean anyObjSelected = CMUtils::ProcessObjectList( inContext, theFlags, FSRefCheckFileOrFolder, &(theThis->implData.checkFlags) ); if( true ) { StBundleResOpen resOpen( theThis->bundleRef ); if( resOpen.IsValid() ) { //Quick Access menu if(theThis->implData.qaActive) { StAEDesc theSubmenuCommands; err = ::AECreateList( NULL, 0, false, theSubmenuCommands ); if( err == noErr ) { Boolean addSeparator = false; Boolean addedItem = false; if(anyObjSelected) { short strIndx = kAddItemStrIndx; if( (theFlags & kListOutMultipleObjects) != 0) strIndx = kAddItemsStrIndx; err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, strIndx, kQAAddItemCommand ); addSeparator = (err == noErr); addedItem = (err == noErr); } theThis->implData.currContext = kCCQuickAccess; Boolean filesAdded = PopulateItemsMenu(theSubmenuCommands, theThis->implData, addSeparator); addedItem = (addedItem || filesAdded); if(addedItem == false) //kMenuItemAttrDisabled does not seem to work, but luckily kMenuItemAttrSectionHeader does work err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, kItemsNotAddedToQAStrIndx, kNoCommand, kMenuItemAttrSectionHeader );//guide item err = CMUtils::AddSubmenu( outCommandPairs, kCommandStrings, kQuickAcessStrIndex, theSubmenuCommands ); } } //Move To menu if(theThis->implData.mtActive) { if(anyObjSelected) { StAEDesc theSubmenuCommands; err = ::AECreateList( NULL, 0, false, theSubmenuCommands ); if( err == noErr ) { Boolean addSeparator = false; Boolean addedItem = false; short strIndx = kAddFolderStrIndx; if( (theFlags & kListOutMultipleObjects) != 0) strIndx = kAddFoldersStrIndx; if( ((theThis->implData.checkFlags & kChFFolderItem) != 0) && ((theThis->implData.checkFlags & kChFNonFolderItem) == 0) && (theThis->implData.checkFlags & kChFVolLocked) == 0 ) {//when item is a folder we may add it, but only when volume not locked err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, strIndx, kMTAddFolderCommand ); addSeparator = (err == noErr); addedItem = (err == noErr); } theThis->implData.currContext = kCCMoveTo; Boolean foldersAdded = PopulateItemsMenu(theSubmenuCommands, theThis->implData, addSeparator); addedItem = (addedItem || foldersAdded); if(addedItem == false) err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, kItemsNotAddedToMTStrIndx, kNoCommand, kMenuItemAttrSectionHeader );//guide item err = CMUtils::AddSubmenu( outCommandPairs, kCommandStrings, kMoveToStrIndex, theSubmenuCommands ); } } else {//guide those little lost lambs bool addHintText = false; if(theThis->implData.mtItems != NULL) addHintText = (theThis->implData.mtItems->GetCount() == 0); else addHintText = true; if(addHintText) { StAEDesc theSubmenuCommands; err = ::AECreateList( NULL, 0, false, theSubmenuCommands ); if( err == noErr ) { err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, kItemsNotAddedToMTStrIndx, kNoCommand, kMenuItemAttrSectionHeader ); err = CMUtils::AddSubmenu( outCommandPairs, kCommandStrings, kMoveToStrIndex, theSubmenuCommands ); } } } } //Copy To menu if(theThis->implData.ctActive) { if(anyObjSelected) { StAEDesc theSubmenuCommands; err = ::AECreateList( NULL, 0, false, theSubmenuCommands ); if( err == noErr ) { Boolean addSeparator = false; Boolean addedItem = false; short strIndx = kAddFolderStrIndx; if( (theFlags & kListOutMultipleObjects) != 0) strIndx = kAddFoldersStrIndx; if( ((theThis->implData.checkFlags & kChFFolderItem) != 0) && ((theThis->implData.checkFlags & kChFNonFolderItem) == 0) && (theThis->implData.checkFlags & kChFVolLocked) == 0 ) {//when item is a folder we may add it, but only when volume not locked err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, strIndx, kCTAddFolderCommand ); addSeparator = (err == noErr); addedItem = (err == noErr); } theThis->implData.currContext = kCCCopyTo; Boolean foldersAdded = PopulateItemsMenu(theSubmenuCommands, theThis->implData, addSeparator); addedItem = (addedItem || foldersAdded); if(addedItem == false) err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, kItemsNotAddedToCTStrIndx, kNoCommand, kMenuItemAttrSectionHeader );//guide item err = CMUtils::AddSubmenu( outCommandPairs, kCommandStrings, kCopyToStrIndex, theSubmenuCommands ); } } else {//guide those little lost lambs bool addHintText = false; if(theThis->implData.ctItems != NULL) addHintText = (theThis->implData.ctItems->GetCount() == 0); else addHintText = true; if(addHintText) { StAEDesc theSubmenuCommands; err = ::AECreateList( NULL, 0, false, theSubmenuCommands ); if( err == noErr ) { err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, kItemsNotAddedToCTStrIndx, kNoCommand, kMenuItemAttrSectionHeader ); err = CMUtils::AddSubmenu( outCommandPairs, kCommandStrings, kCopyToStrIndex, theSubmenuCommands ); } } } } //Alias To menu if(theThis->implData.atActive) { if(anyObjSelected) { StAEDesc theSubmenuCommands; err = ::AECreateList( NULL, 0, false, theSubmenuCommands ); if( err == noErr ) { Boolean addSeparator = false; Boolean addedItem = false; short strIndx = kAddFolderStrIndx; if( (theFlags & kListOutMultipleObjects) != 0) strIndx = kAddFoldersStrIndx; if( ((theThis->implData.checkFlags & kChFFolderItem) != 0) && ((theThis->implData.checkFlags & kChFNonFolderItem) == 0) && (theThis->implData.checkFlags & kChFVolLocked) == 0 ) {//when item is a folder we may add it, but only when volume not locked err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, strIndx, kATAddFolderCommand ); addSeparator = (err == noErr); addedItem = (err == noErr); } theThis->implData.currContext = kCCAliasTo; Boolean foldersAdded = PopulateItemsMenu(theSubmenuCommands, theThis->implData, addSeparator); addedItem = (addedItem || foldersAdded); if(addedItem == false) err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, kItemsNotAddedToATStrIndx, kNoCommand, kMenuItemAttrSectionHeader );//guide item err = CMUtils::AddSubmenu( outCommandPairs, kCommandStrings, kAliasToStrIndex, theSubmenuCommands ); } } else {//guide those little lost lambs bool addHintText = false; if(theThis->implData.atItems != NULL) addHintText = (theThis->implData.atItems->GetCount() == 0); else addHintText = true; if(addHintText) { StAEDesc theSubmenuCommands; err = ::AECreateList( NULL, 0, false, theSubmenuCommands ); if( err == noErr ) { err = CMUtils::AddResCommand( theSubmenuCommands, kCommandStrings, kItemsNotAddedToATStrIndx, kNoCommand, kMenuItemAttrSectionHeader ); err = CMUtils::AddSubmenu( outCommandPairs, kCommandStrings, kAliasToStrIndex, theSubmenuCommands ); } } } } } } } catch(...) { err = -1; } return err; } // --------------------------------------------------------------------------- // CMPluginHandleSelection // --------------------------------------------------------------------------- // Carry out the command that the user selected. The commandID indicates // which command was selected OSStatus CMPluginHandleSelection( void *thisInstance, AEDesc *inContext, SInt32 inCommandID ) { TRACE_STR( "\pQuickAccessCM->CMPluginHandleSelection" ); AbstractCMPluginType *theThis = (AbstractCMPluginType *)thisInstance; OSStatus err = noErr; try { if(inCommandID == kQAAddItemCommand) { UInt32 theFlags = kListClear; theThis->implData.currContext = kCCQuickAccess; CMUtils::ProcessObjectList( inContext, theFlags, FSRefProcessFileOrFolder, &(theThis->implData) ); } else if(inCommandID == kMTAddFolderCommand) { UInt32 theFlags = kListClear; theThis->implData.currContext = kCCMoveTo; CMUtils::ProcessObjectList( inContext, theFlags, FSRefProcessFileOrFolder, &(theThis->implData) ); } else if(inCommandID == kCTAddFolderCommand) { UInt32 theFlags = kListClear; theThis->implData.currContext = kCCCopyTo; CMUtils::ProcessObjectList( inContext, theFlags, FSRefProcessFileOrFolder, &(theThis->implData) ); } else if(inCommandID == kATAddFolderCommand) { UInt32 theFlags = kListClear; theThis->implData.currContext = kCCAliasTo; CMUtils::ProcessObjectList( inContext, theFlags, FSRefProcessFileOrFolder, &(theThis->implData) ); } else if( inCommandID >= kQAItemStartCommand ) { UInt32 theContext = kCCNothing; CFAliasAndNameArray *theItemList = NULL; SInt32 theIndex = 0; if( inCommandID < kMTItemStartCommand ) { theContext = kCCQuickAccess; theItemList = theThis->implData.qaItems; theIndex = inCommandID - kQAItemStartCommand; } else if( (inCommandID >= kMTItemStartCommand) && (inCommandID < kCTItemStartCommand) ) { theContext = kCCMoveTo; theItemList = theThis->implData.mtItems; theIndex = inCommandID - kMTItemStartCommand; } else if( (inCommandID >= kCTItemStartCommand) && (inCommandID < kATItemStartCommand) ) { theContext = kCCCopyTo; theItemList = theThis->implData.ctItems; theIndex = inCommandID - kCTItemStartCommand; } else if( inCommandID >= kATItemStartCommand ) { theContext = kCCAliasTo; theItemList = theThis->implData.atItems; theIndex = inCommandID - kATItemStartCommand; } if( (theContext == kCCNothing) || (theItemList == NULL) ) return noErr; FSRef newRef; if( theItemList->FetchFSRefWithMountingAt(theIndex, newRef) ) { UInt32 modifiers = ::GetCurrentKeyModifiers(); if( (modifiers & optionKey) != 0 ) {//option key pressed - remove item instead of opening it theItemList->RemoveItemAt(theIndex);//removing the item releases it } else { if( theContext == kCCQuickAccess ) { err = ::LSOpenFSRef( &newRef, NULL); } else if( (theContext == kCCMoveTo) || (theContext == kCCCopyTo) || (theContext == kCCAliasTo) ) { Boolean showDest = ((modifiers & shiftKey) != 0);//shift key forces showing of destination folder after copy/move showDest = showDest || (((theContext == kCCMoveTo) && theThis->implData.mtShowDest) || ((theContext == kCCCopyTo) && theThis->implData.ctShowDest) || ((theContext == kCCAliasTo) && theThis->implData.atShowDest)); if( CheckForExistingItemsInFolder(theThis, inContext, newRef) ) {//OK to move, copy, oralias to destination StAEDesc folderDesc; err = CMUtils::CreateAliasDesc( &newRef, folderDesc ); if(err == noErr) { if( (theContext == kCCMoveTo) && ((theThis->implData.checkFlags & kChFVolLocked) == 0) ) { TRACE_STR( "\pQuickAccessCM->CMPluginHandleSelection: about to move items" ); err = CMUtils::SendAEWithTwoObjToFinder( kAECoreSuite, kAEMove, keyDirectObject, *inContext, keyAEInsertHere, folderDesc, false, false ); /* SInt32 sysVersion; ::Gestalt(gestaltSystemVersion, &sysVersion); if(sysVersion >= 0x1020) {//in Jaguar moving from disc to disc actually performs copy - check if orignals still exist in source folder //Finder did not finish its job yet, there is no way to check if originals will exist after move } */ } else if( (theContext == kCCMoveTo) || (theContext == kCCCopyTo) ) {//"move" does not work on locked volume, use "duplicate", aka "clone" :-) TRACE_STR( "\pQuickAccessCM->CMPluginHandleSelection: volume locked, cloning object" ); err = CMUtils::SendAEWithTwoObjToFinder( kAECoreSuite, kAEClone, keyDirectObject, *inContext, keyAEInsertHere, folderDesc, false, false ); } else { TRACE_STR( "\pQuickAccessCM->CMPluginHandleSelection: about to alias items" ); OSType anAliasType = typeAlias; AEDesc typeDesc = {typeNull,nil}; err = ::AECreateDesc( typeType, &anAliasType, sizeof(OSType), &typeDesc); if(err == noErr) { TRACE_STR( "\pQuickAccessCM->CMPluginHandleSelection: about to call SendAEWithThreeObjToFinder" ); err = CMUtils::SendAEWithThreeObjToFinder( kAECoreSuite, kAECreateElement, keyAEObjectClass, typeDesc, keyASPrepositionTo /* keyDirectObject */, *inContext, keyAEInsertHere, folderDesc, false, false ); ::AEDisposeDesc( &typeDesc); } } if( (err == noErr) && showDest ) { err = ::LSOpenFSRef( &newRef, NULL); //building a list of dest items is not trivial because Finder did not perform a copy yet /* UInt32 theFlags = 0;//kProcBreakOnFirst; ExistingObjects theRec; theRec.destFolderRef = &newRef; CMUtils::ProcessObjectList( inContext, theFlags, FSRefObjectExists, &theRec); if(destItemsExist) { //reveal items in destination folder CMUtils::SendAppleEventToFinder( kAEMiscStandards, kAEMakeObjectsVisible, theRec.existingObjList, false ); } */ } } } } } } else //item could not be resolved - remove it now { StBundleResOpen resOpen( theThis->bundleRef ); if( resOpen.IsValid() ) { CFStringRef removeStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kRemoveStrIndex); CFObjDel removeStrDel(removeStr); CFStringRef cancelStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kCancelStrIndex); CFObjDel cancelStrDel(cancelStr); CFStringRef alertStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kDestNoFoundStrIndex); CFObjDel alertStrDel(alertStr); Boolean okToDelete = DisplayWarning( alertStr, removeStr, cancelStr ); if(okToDelete) theItemList->RemoveItemAt(theIndex); } } } } catch(...) { err = -1; } return err; } // --------------------------------------------------------------------------- // CMPluginPostMenuCleanup // --------------------------------------------------------------------------- void CMPluginPostMenuCleanup( void *thisInstance ) { try { TRACE_STR( "\pQuickAccessCM->CMPluginPostMenuCleanup: begin" ); AbstractCMPluginType *theThis = (AbstractCMPluginType *)thisInstance; SavePreferences(theThis->implData); delete theThis->implData.qaItems; theThis->implData.qaItems = NULL; delete theThis->implData.mtItems; theThis->implData.mtItems = NULL; delete theThis->implData.ctItems; theThis->implData.ctItems = NULL; delete theThis->implData.atItems; theThis->implData.atItems = NULL; TRACE_STR( "\pQuickAccessCM->CMPluginPostMenuCleanup: end" ); } catch(...) { } } #pragma mark - OSStatus FSRefCheckFileOrFolder(const FSRef *inRef, void *ioData) { if( (inRef == NULL) || (ioData == NULL) ) return paramErr; UInt16 *outFlags = (UInt16 *)ioData; //we are interested in real files or folders //sometimes we get FSRef not valid (InternetExplorer) FSCatalogInfo theInfo; theInfo.nodeFlags = 0; theInfo.volume = 0; OSErr err = ::FSGetCatalogInfo(inRef, kFSCatInfoNodeFlags | kFSCatInfoVolume, &theInfo, NULL, NULL, NULL); if ( err == noErr ) { #if _DEBUG_ { Str255 hexString; ByteCount destLen = sizeof(Str255); CMUtils::BufToHex( (const unsigned char *)&theInfo.nodeFlags, (char *)hexString, sizeof(theInfo.nodeFlags), destLen ); ::CopyCStringToPascal( (char *)hexString, hexString); DEBUG_STR( "\pQuickAccessCM->FSRefCheckFileOrFolder: dumping nodeFlags:" ); DEBUG_STR(hexString); } #endif //_DEBUG_ if( (theInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0 ) { *outFlags |= kChFFolderItem; } else { *outFlags |= kChFNonFolderItem; } FSVolumeInfo volInfo; volInfo.flags = 0; err = ::FSGetVolumeInfo(theInfo.volume, 0, NULL, kFSVolInfoFlags, &volInfo, NULL, NULL); if ( err == noErr ) { #if _DEBUG_ { Str255 hexString; ByteCount destLen = sizeof(Str255); CMUtils::BufToHex( (const unsigned char *)&volInfo.flags, (char *)hexString, sizeof(volInfo.flags), destLen ); ::CopyCStringToPascal( (char *)hexString, hexString); DEBUG_STR( "\pQuickAccessCM->FSRefCheckFileOrFolder: dumping volInfo.flags:" ); DEBUG_STR(hexString); } #endif //_DEBUG_ if( ((volInfo.flags & kFSVolFlagHardwareLockedMask) != 0) || ((volInfo.flags & kFSVolFlagSoftwareLockedMask) != 0) ) { *outFlags |= kChFVolLocked; } } } return err; } OSStatus FSRefProcessFileOrFolder(const FSRef *inRef, void *ioData) { TRACE_STR( "\pQuickAccessCM->ProcessFileOrFolder" ); if( (inRef == NULL) || (ioData == NULL) ) return paramErr; CMPluginImplData *theData = (CMPluginImplData *)ioData; OSStatus err = noErr; #if _DEBUG_ CFURLRef urlRef = ::CFURLCreateFromFSRef( kCFAllocatorDefault, inRef ); CFObjDel urlDel(urlRef); if(urlRef != NULL) { CFStringRef strRef = ::CFURLGetString( urlRef ); if(strRef != NULL) { Str255 theString; if( ::CFStringGetPascalString(strRef, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { DEBUG_STR( theString ); } else { DEBUG_STR( "\pQuickAccessCM. CFStringGetPascalString failed" ); } } else { DEBUG_STR( "\pQuickAccessCM. CFURLGetString failed" ); } } else { DEBUG_STR( "\pQuickAccessCM. CFURLCreateFromFSRef failed" ); } #endif //_DEBUG_ FSCatalogInfo theInfo; err = ::FSGetCatalogInfo(inRef, kFSCatInfoNodeFlags, &theInfo, NULL, NULL, NULL); if ( err == noErr ) { //do something useful to the file/folder TRACE_STR( "\pQuickAccessCM. Executing now!" ); AddItemToList(theData, inRef); } else { DEBUG_STR( "\pQuickAccessCM. FSGetCatalogInfo failed" ); } return err; } //Situation: we want to place an object in another folder. //we need to check if there is no object with the same name in destination folder. //designed to be handler function called by CMUtils::ProcessObjectList //inRef is a reference to the original object - we take its name and place in destination folder looking for conflicts //inData must be a pointer to ExistingObjects structure providing //a parent folder and alias list to add existing items to // NULL as parent folder reference - the the original FSRef is validated //we return noErr if the object in question exists OSStatus FSRefObjectExists(const FSRef *inRef, void *ioData) { if(inRef == NULL || ioData == NULL) return noErr; ExistingObjects *theRec = (ExistingObjects*)ioData; FSRef *parentRef = theRec->destFolderRef; FSCatalogInfo theFileInfo; OSStatus err; if(parentRef == NULL)//no destination parent defined - we want to check original object itself {//check if FSRef itself is valid err = ::FSGetCatalogInfo( inRef, kFSCatInfoNodeFlags, &theFileInfo, NULL, NULL, NULL); if( err == noErr ) { theRec->existingObjList.AddItem( inRef ); } } else { HFSUniStr255 uniFileName; uniFileName.length = 0; err = ::FSGetCatalogInfo( inRef, kFSCatInfoNodeFlags | kFSCatInfoTextEncoding, &theFileInfo, &uniFileName, NULL, NULL); if(err == noErr) { FSRef newRef; err = ::FSMakeFSRefUnicode( parentRef, uniFileName.length, uniFileName.unicode, theFileInfo.textEncodingHint, &newRef); if( err == noErr ) { theRec->existingObjList.AddItem( &newRef ); } } } return err; } OSStatus FSRefDeleteObject(const FSRef *inRef, void */*ioData*/) { if(inRef == NULL) return noErr; return CMUtils::DeleteObject(inRef); } Boolean CheckForExistingItemsInFolder(const AbstractCMPluginType *theThis, const AEDescList *fileList, FSRef &inFolderRef) { TRACE_STR( "\pQuickAccessCM. checking for duplicates in dest folder" ); //check if any object stands in our way UInt32 theFlags = 0;//kProcBreakOnFirst; - do not break on first, we want to build a full list of duplicates ExistingObjects theRec; theRec.destFolderRef = &inFolderRef; Boolean duplicateExists = CMUtils::ProcessObjectList( fileList, theFlags, FSRefObjectExists, &theRec); if(duplicateExists) {//we need to ask user what s/he wants to do TRACE_STR( "\pQuickAccessCM. Duplicates in dest folder found" ); TRACE_STR( "\pQuickAccessCM. About to ask user what next" ); if( AskUserIfOKToDelete(theThis) ) { TRACE_STR( "\pQuickAccessCM. About to delete items" ); //try to delete items UInt32 theFlags = 0; Boolean allDeletedFine = CMUtils::ProcessObjectList( theRec.existingObjList, theFlags, FSRefDeleteObject, NULL); if(allDeletedFine) { ExistingObjects newRec; newRec.destFolderRef = &inFolderRef; //check once again to see if items were deleted indeed theFlags = 0;//kProcBreakOnFirst; duplicateExists = CMUtils::ProcessObjectList( fileList, theFlags, FSRefObjectExists, &newRec ); if(duplicateExists) {//inform user that some items could not be deleted TellUserWeFailedToDelete(theThis); } } else { TellUserWeFailedToDelete(theThis); } } } return (!duplicateExists); } Boolean AskUserIfOKToDelete(const AbstractCMPluginType *theThis) { StBundleResOpen resOpen( theThis->bundleRef ); if( resOpen.IsValid() == false ) return false; CFStringRef replaceStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kReplaceStrIndex); CFObjDel replStrDel(replaceStr); CFStringRef cancelStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kCancelStrIndex); CFObjDel cancelStrDel(cancelStr); CFStringRef alertStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kItemsExistStrIndex); CFObjDel alertStrDel(alertStr); DialogRef theAlert = NULL; AlertStdCFStringAlertParamRec params; ::GetStandardAlertDefaultParams(¶ms, kStdCFStringAlertVersionOne); params.movable = true; // params.helpButton = false; params.defaultText = replaceStr;//CFSTR("Replace"); params.cancelText = cancelStr;//CFSTR("Cancel"); // params.otherText = NULL; params.defaultButton = kAlertStdAlertCancelButton; // params.cancelButton = 0; params.position = kWindowDefaultPosition; // params.flags = 0; // CFStringRef alertText = CFSTR("One or more items already exist at this location."); OSStatus err = ::CreateStandardAlert( kAlertCautionAlert, alertStr, NULL, ¶ms, &theAlert ); if( (err == noErr) && (theAlert != NULL) ) { DialogItemIndex hitItem = kAlertStdAlertCancelButton; err = ::RunStandardAlert( theAlert, NULL, &hitItem ); if( (err == noErr) && (hitItem == kAlertStdAlertOKButton) ) { return true; } } return false; } void TellUserWeFailedToDelete(const AbstractCMPluginType *theThis) { StBundleResOpen resOpen( theThis->bundleRef ); if( resOpen.IsValid() == false ) return; CFStringRef theButtonStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kTooBadStrIndex); CFObjDel butStrDel(theButtonStr); CFStringRef alertStr = CMUtils::CreateCFStringFromResourceText(kDialogStrings, kCouldNotDeleteStrIndex); CFObjDel alertStrDel(alertStr); DialogRef theAlert = NULL; AlertStdCFStringAlertParamRec params; ::GetStandardAlertDefaultParams(¶ms, kStdCFStringAlertVersionOne); params.movable = true; params.defaultText = theButtonStr;//CFSTR("Too bad"); params.position = kWindowDefaultPosition; //CFStringRef alertText = CFSTR("One or more items in destination folder could not be deleted. Operation failed."); OSStatus err = ::CreateStandardAlert( kAlertCautionAlert, alertStr, NULL, ¶ms, &theAlert ); if( (err == noErr) && (theAlert != NULL) ) { DialogItemIndex hitItem = kAlertStdAlertCancelButton; err = ::RunStandardAlert( theAlert, NULL, &hitItem ); } } #pragma mark - void AddItemToList(CMPluginImplData *ioData, const FSRef *inRef) { TRACE_STR( "\pQuickAccessCM. AddItemToList" ); if(inRef == NULL) return; if(ioData->currContext == kCCQuickAccess) { if(ioData->qaItems == NULL)//not created yet ioData->qaItems = new CFAliasAndNameArray(); ioData->qaItems->AddPair(inRef, NULL); } else if(ioData->currContext == kCCMoveTo) { if(ioData->mtItems == NULL)//not created yet ioData->mtItems = new CFAliasAndNameArray(); ioData->mtItems->AddPair(inRef, NULL); } else if(ioData->currContext == kCCCopyTo) { if(ioData->ctItems == NULL)//not created yet ioData->ctItems = new CFAliasAndNameArray(); ioData->ctItems->AddPair(inRef, NULL); } else if(ioData->currContext == kCCAliasTo) { if(ioData->atItems == NULL)//not created yet ioData->atItems = new CFAliasAndNameArray(); ioData->atItems->AddPair(inRef, NULL); } } Boolean PopulateItemsMenu(AEDescList* ioCommands, const CMPluginImplData &inData, Boolean addSeparator) { TRACE_STR( "\pQuickAccessCM. PopulateItemsMenu" ); Boolean addedItem = false; CFAliasAndNameArray *theItemList = NULL; if(inData.currContext == kCCQuickAccess) theItemList = inData.qaItems; else if(inData.currContext == kCCMoveTo) theItemList = inData.mtItems; else if(inData.currContext == kCCCopyTo) theItemList = inData.ctItems; else if(inData.currContext == kCCAliasTo) theItemList = inData.atItems; if(theItemList == NULL) return false; bool disableFancyFeatures = (inData.sysVersion < 0x1020);//if OS older than 10.2 CFStringRef removeStr = NULL; if(disableFancyFeatures == false) { removeStr = CMUtils::CreateCFStringFromResourceText(kCommandStrings, kRemoveStrIndx); } ACFString strDel(removeStr, false); OSStatus err = noErr; CFIndex theCount = theItemList->GetCount(); for(CFIndex i = 0; i < theCount; i++) { CFStringRef theName = theItemList->FetchNameAt(i); if( theName != NULL ) { if(addSeparator) { err = CMUtils::AddResCommand( ioCommands, kCommandStrings, kSeparatorStrIndx, 0 ); addSeparator = false; } SInt32 commandID = 0; if(inData.currContext == kCCQuickAccess) commandID = kQAItemStartCommand+i; else if(inData.currContext == kCCMoveTo) commandID = kMTItemStartCommand+i; else if(inData.currContext == kCCCopyTo) commandID = kCTItemStartCommand+i; else if(inData.currContext == kCCAliasTo) commandID = kATItemStartCommand+i; if(disableFancyFeatures) { CMUtils::AddCommandToAEDescList_Compatible( theName, commandID, ioCommands); } else { CMUtils::AddCommandToAEDescList( theName, commandID, true, ioCommands, kMenuItemAttrDynamic | kMenuItemAttrNotPreviousAlternate); if(removeStr != NULL) { ACFMutableString newStr(removeStr); newStr.Append(theName); CMUtils::AddCommandToAEDescList( newStr, commandID, true, ioCommands, kMenuItemAttrDynamic | kMenuItemAttrIgnoreMeta, kMenuOptionModifier); } } addedItem = true; } /* else {//alias could not be resolved, delete item (this no longer works in new architecture - alias is not resolved here) theItemList->RemoveItemAt(i); theCount = theItemList->GetCount(); i--; } */ } return addedItem; } void ReadPreferences(CMPluginImplData &ioData) { TRACE_STR( "\pQuickAccessCM->ReadPreferences" ); CFStringRef prefsIdentifier = CFSTR(CM_IMPL_PLUGIN_PREFS_INDENTIFIER); ::CFPreferencesAppSynchronize( prefsIdentifier ); Boolean isValid = false; CFIndex theState = 0; CFIndex theVer = ::CFPreferencesGetAppIntegerValue( CFSTR("VERSION"), prefsIdentifier, &isValid ); if( isValid && (theVer != 0) ) { isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("ACTIVE_QA"), prefsIdentifier, &isValid ); if( isValid ) ioData.qaActive = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("ACTIVE_MT"), prefsIdentifier, &isValid ); if( isValid ) ioData.mtActive = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("ACTIVE_CT"), prefsIdentifier, &isValid ); if( isValid ) ioData.ctActive = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("ACTIVE_AT"), prefsIdentifier, &isValid ); if( isValid ) ioData.atActive = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("SHOW_MT_DEST"), prefsIdentifier, &isValid ); if( isValid ) ioData.mtShowDest = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("SHOW_CT_DEST"), prefsIdentifier, &isValid ); if( isValid ) ioData.ctShowDest = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("SHOW_AT_DEST"), prefsIdentifier, &isValid ); if( isValid ) ioData.atShowDest = (theState != 0); ioData.qaItems = new CFAliasAndNameArray(CFSTR("QUICK_ACCESS_ITEMS"), prefsIdentifier, theVer); ioData.mtItems = new CFAliasAndNameArray(CFSTR("MOVE_TO_ITEMS"), prefsIdentifier, theVer); ioData.ctItems = new CFAliasAndNameArray(CFSTR("COPY_TO_ITEMS"), prefsIdentifier, theVer); ioData.atItems = new CFAliasAndNameArray(CFSTR("ALIAS_TO_ITEMS"), prefsIdentifier, theVer); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("QA_USE_HIER_MENUS"), prefsIdentifier, &isValid ); if( isValid ) ioData.qaUseHierMenus = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("MT_USE_HIER_MENUS"), prefsIdentifier, &isValid ); if( isValid ) ioData.mtUseHierMenus = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("CT_USE_HIER_MENUS"), prefsIdentifier, &isValid ); if( isValid ) ioData.ctUseHierMenus = (theState != 0); isValid = false; theState = ::CFPreferencesGetAppIntegerValue(CFSTR("AT_USE_HIER_MENUS"), prefsIdentifier, &isValid ); if( isValid ) ioData.atUseHierMenus = (theState != 0); } } void SavePreferences(CMPluginImplData &ioData) { TRACE_STR( "\pQuickAccessCM->SavePreferences" ); CFStringRef prefsIdentifier = CFSTR(CM_IMPL_PLUGIN_PREFS_INDENTIFIER); Boolean isValid = false; CFIndex theVer = ::CFPreferencesGetAppIntegerValue( CFSTR("VERSION"), prefsIdentifier, &isValid ); if( !isValid || (theVer != 2) ) {//first time upgrade version and init other keys theVer = 2; ACFNumber theNum((short)theVer); ::CFPreferencesSetAppValue( CFSTR("VERSION"), (CFPropertyListRef)(CFNumberRef)theNum, prefsIdentifier ); ACFNumber qaNum((short)ioData.qaActive); ::CFPreferencesSetAppValue( CFSTR("ACTIVE_QA"), (CFPropertyListRef)(CFNumberRef)qaNum, prefsIdentifier ); ACFNumber mtNum((short)ioData.mtActive); ::CFPreferencesSetAppValue( CFSTR("ACTIVE_MT"), (CFPropertyListRef)(CFNumberRef)mtNum, prefsIdentifier ); ACFNumber ctNum((short)ioData.ctActive); ::CFPreferencesSetAppValue( CFSTR("ACTIVE_CT"), (CFPropertyListRef)(CFNumberRef)ctNum, prefsIdentifier ); ACFNumber atNum((short)ioData.atActive); ::CFPreferencesSetAppValue( CFSTR("ACTIVE_AT"), (CFPropertyListRef)(CFNumberRef)atNum, prefsIdentifier ); ACFNumber smtNum((short)ioData.mtShowDest); ::CFPreferencesSetAppValue( CFSTR("SHOW_MT_DEST"), (CFPropertyListRef)(CFNumberRef)smtNum, prefsIdentifier ); ACFNumber sctNum((short)ioData.ctShowDest); ::CFPreferencesSetAppValue( CFSTR("SHOW_CT_DEST"), (CFPropertyListRef)(CFNumberRef)sctNum, prefsIdentifier ); ACFNumber satNum((short)ioData.atShowDest); ::CFPreferencesSetAppValue( CFSTR("SHOW_AT_DEST"), (CFPropertyListRef)(CFNumberRef)satNum, prefsIdentifier ); ACFNumber qahmNum((short)ioData.qaUseHierMenus); ::CFPreferencesSetAppValue( CFSTR("QA_USE_HIER_MENUS"), (CFPropertyListRef)(CFNumberRef)qahmNum, prefsIdentifier ); ACFNumber mthmNum((short)ioData.mtUseHierMenus); ::CFPreferencesSetAppValue( CFSTR("MT_USE_HIER_MENUS"), (CFPropertyListRef)(CFNumberRef)mthmNum, prefsIdentifier ); ACFNumber cthmNum((short)ioData.ctUseHierMenus); ::CFPreferencesSetAppValue( CFSTR("CT_USE_HIER_MENUS"), (CFPropertyListRef)(CFNumberRef)cthmNum, prefsIdentifier ); ACFNumber athmNum((short)ioData.atUseHierMenus); ::CFPreferencesSetAppValue( CFSTR("AT_USE_HIER_MENUS"), (CFPropertyListRef)(CFNumberRef)athmNum, prefsIdentifier ); } if(ioData.qaItems != NULL) { ioData.qaItems->SaveArrayToPrefs(CFSTR("QUICK_ACCESS_ITEMS"), prefsIdentifier ); } if(ioData.mtItems != NULL) { ioData.mtItems->SaveArrayToPrefs(CFSTR("MOVE_TO_ITEMS"), prefsIdentifier ); } if(ioData.ctItems != NULL) { ioData.ctItems->SaveArrayToPrefs(CFSTR("COPY_TO_ITEMS"), prefsIdentifier ); } if(ioData.atItems != NULL) { ioData.atItems->SaveArrayToPrefs(CFSTR("ALIAS_TO_ITEMS"), prefsIdentifier ); } ::CFPreferencesAppSynchronize( prefsIdentifier ); } //returns true if OK hit Boolean DisplayWarning( CFStringRef inWarning, CFStringRef inOKString, CFStringRef inCancelStr ) { if(inWarning == NULL) { TRACE_STR( "\pQuickAccessCM->DisplayWarning null text for alert - exit" ); return true; } CFIndex theLen = ::CFStringGetLength(inWarning); if(theLen == 0) { TRACE_STR( "\pQuickAccessCM->DisplayWarning empty text for alert - exit" ); return true; } DialogRef theAlert = NULL; AlertStdCFStringAlertParamRec params; //Fills out an AlertStdCFStringAlertParamRec with default values: //- not movable //- no help button //- default button with title "OK" //- no cancel or other buttons. ::GetStandardAlertDefaultParams(¶ms, kStdCFStringAlertVersionOne); params.movable = true; // params.helpButton = false; if(inOKString != NULL) params.defaultText = inOKString; if(inCancelStr != NULL) { params.cancelText = inCancelStr; params.cancelButton = kAlertStdAlertCancelButton; } // params.otherText = NULL; // params.defaultButton = kAlertStdAlertOKButton; params.position = kWindowDefaultPosition; // params.flags = 0; OSStatus err = ::CreateStandardAlert( kAlertCautionAlert, inWarning, NULL, ¶ms, &theAlert ); if( (err == noErr) && (theAlert != NULL) ) { DialogItemIndex hitItem = kAlertStdAlertCancelButton; err = ::RunStandardAlert( theAlert, NULL, &hitItem ); if( (err == noErr) && (hitItem == kAlertStdAlertOKButton) ) { return true; } } else { TRACE_STR( "\pQuickAccessCM->DisplayWarning error creating alert" ); } return false; }