#include "cRecentDocMenuAttachment.h" #include #include "MoreFilesExtras.h" // From Jim Luther's MoreFiles package // =========================================================================== // cRecentDocMenuAttachment.cpp Version 1.1 ©1998 Joakim Braun All rights reserved. // =========================================================================== // // CONTENTS: cDocListMenu: A PowerPlant LAttachment subclass that manages a documents menu. // Provides facilities for "registering" and "unregistering" documents, // as well as handling menu selections by calling LDocApplication::SendAEOpenDoc(). // cSavedDocListMenu: A subclass of cDocListMenu that reads and writes the cDocListMenu state // into application's preferences file. Useful as a recent document menu. // cDirectoryDocListMenu: A subclass of cDocListMenu that scans a directory for files of a certain type. // cAppDirectoryDocListMenu: A subclass of cDirectoryDocListMenu that scans the parent directory // of the application for files of a certain type. // cInsertDocDirectoryDocListMenu: A subclass of cDirectoryDocListMenu that stores // an "insert document" CommandT. When user chooses a menu item, we check if a document // is active that accepts the "insert document" command. If so, we call its ObeyCommand() // function with the stored CommandT and the respective FSSpec as ioParam. // cInsertAppDirDocMenu: Same as cInsertDocDirectoryDocListMenu, except it scans application file's directory. // cInsertAppDirDocMenuWithChoice: Same as cInsertAppDirDocMenu, except it manages a "Choose file" item // at bottom of menu. // REQUIRES: my cDocAlias class (included in the archive containing this file). // Jim Luther's MoreFiles package (to be found on any infoMac site). // HOW TO USE: cSavedDocListMenu // If you'd like to have a recent documents menu that's restored between application sessions, // do like this: // * Your application must derive from LDocApplication. // * After your LMenuBar (or derived class) has been constructed, // add a cSavedDocListMenu attachment to the application object. // You'll want to save a pointer to the cSavedDocListMenu as a member variable. So you write: // mRecentDocMenuPtr = new cSavedDocListMenu(kRecentDocMenuID, kRecentDocMaxCount, "\pMy preferences file name", true, true); // AddAttachment(mRecentDocMenuPtr); // This will create a recent doc menu object that looks for the 'rDoc' 128 resource // in your app's preferences file and recreates itself from that. Upon destruction, it will // update the resource in the preferences file to reflect the current menu contents. // When user chooses a document in the menu, your application object's SendAEOpenDoc() function gets called. // * Whenever a file is opened, call RegisterFile(). Typically, you'll do that // in your app's OpenDocument() function: // mRecentDocMenuPtr->RegisterFile(*inMacFSSpec); // NOTE: Newly created and saved documents will not be added to the menu using the above strategy - only opened docs. // Another strategy is to register the document each time a save operation happens. // The framework is provided, the details are up to you. // HOW TO USE: cAppDirectoryDocListMenu // If you'd like to have a menu with documents of a certain file type that are in the same directory // as your application, do like this: // * Your application must derive from LDocApplication. // * After your LMenuBar (or derived class) has been constructed, // add a cAppDirectoryDocListMenu attachment to the application object. // AddAttachment(new cAppDirectoryDocListMenu(kAppDirDocMenuID, kAppDirDocMaxCount, kMyDocumentFileType, false); // When user chooses a document in the menu, your application object's SendAEOpenDoc() function gets called. // These classes are free for any and all use. // Do not distribute modified source code under my name. // No support promised, no liability accepted. Provided "as is". // That said, I can be reached at braun@swipnet.se. // Change history: // 1.0 August 16, 1998 First release // 1.1 August 30, 1998 Changed class names to reflect new, modular design // (if you used cRecentDocMenu before, you'll want to use cDocListMenu; // if you used cSavedRecentDocMenu, you'll want to use cSavedDocListMenu.) // Added static pointer in cSavedDocListMenu // Clarified some points in documentation // Rewrote RebuildMenu() to use PowerPlant menu objects // Fixed bug in RebuildMenu() where some metacharacters in file names // were filtered by Menu manager. // Added directory scanning menu classes // ----------------------------------------------------------------- // Ä Constructor // ----------------------------------------------------------------- // Takes ID of MENU resource, number of recent docs to show in menu. cDocListMenu::cDocListMenu( ResIDT inMenuID, Uint32 inMaxDocCount){ // we want to look at all events mMessage = msg_AnyMessage; // Initialize members mMaxDocCount = inMaxDocCount; mMenuID = inMenuID; } // ----------------------------------------------------------------- // Ä Destructor // ----------------------------------------------------------------- // Deletes cDocAlias objects in array of pointers. cDocListMenu::~cDocListMenu(void){ DeleteAliasArrayItems(); } // ----------------------------------------------------------------- // Ä ExecuteSelf // ----------------------------------------------------------------- // Update menu every time user clicks in menu bar. (Is there a better way...?) // Take care of enabling the items in the menu. // Handle menu selections. (We get the FSSpec connected with a menu item, // then call the virtual function HandleSelectedFSSpec(), which in turn // checks if host is derived from LDocApplication. If so, calls host's SendAEOpenDoc() function.) void cDocListMenu::ExecuteSelf( MessageT inMessage, void *ioParam ) { mExecuteHost = true; switch (inMessage) { // Rebuild menu on menu bar click case msg_Event: EventRecord *event = (EventRecord*) ioParam; WindowRef window; if(event->what == mouseDown && ::FindWindow(event->where, &window) == inMenuBar) RebuildMenu(); break; // Enable our menu and items in it case msg_CommandStatus: mExecuteHost = HandleCommandStatus(*(SCommandStatus*)ioParam); break; // Act on selections from our menu default: { ResIDT synthMenu; Int16 synthItem; // If we have a synthetic menu selection and our menu was selected... if ( LCommander::IsSyntheticCommand( inMessage, synthMenu, synthItem) && synthMenu == mMenuID){ mExecuteHost = HandleDocListMenuChoice(synthItem); } } break; } } // ----------------------------------------------------------------- // Ä SetFileCount() // ----------------------------------------------------------------- // Control how many recent docs to show in menu. void cDocListMenu:: SetFileCount(Uint32 inMaxDocCount){ Uint32 currDocs = mAliasArray.GetCount(); mMaxDocCount = inMaxDocCount; // If we have more items than allowed, // clear items at top of menu if(currDocs > mMaxDocCount){ // Delete items at start of array until we're within the limit TArrayIterator iterator(mAliasArray, LArray::index_First); cDocAlias* theAlias; while(iterator.Current(theAlias) && currDocs > mMaxDocCount) { mAliasArray.Remove(theAlias); delete theAlias; currDocs--; } } } // ----------------------------------------------------------------- // Ä RegisterFile(AliasHandle) // ----------------------------------------------------------------- // Tell cDocListMenu to store an alias to file indicated. // Returns error code from Alias Manager's ResolveAlias() function. // Does not store duplicate references (no trouble calling it twice with the same file). OSErr cDocListMenu:: RegisterFile(AliasHandle inAliasHandle){ OSErr result = noErr; Boolean wasChanged = false; FSSpec theSpec; result = ::ResolveAlias(NULL, inAliasHandle, &theSpec, &wasChanged); // Call FSSpec version of RegisterFile() if(result == noErr) RegisterFile(theSpec); return result; } // ----------------------------------------------------------------- // Ä RegisterFile(LFile&) // ----------------------------------------------------------------- // Tell cDocListMenu to store an alias to file indicated. // Does not store duplicate references (no trouble calling it twice with the same file). void cDocListMenu:: RegisterFile(LFile& inFile){ FSSpec theSpec; inFile.GetSpecifier(theSpec); RegisterFile(theSpec); } // ----------------------------------------------------------------- // Ä RegisterFile(FSSpec&) // ----------------------------------------------------------------- // Tell cDocListMenu to store an alias to file indicated. // Does not store duplicate references (no trouble calling it twice with the same file). void cDocListMenu:: RegisterFile(FSSpec& inFSSpec){ FSSpec currSpec; Boolean registerIt = true; // Walk thru array, see if FSSpec is in any of the cDocAliases TArrayIterator iterator(mAliasArray); cDocAlias* theAlias = NULL; while(iterator.Next(theAlias) && registerIt == true) { theAlias->GetFileSpec(currSpec); if(LFile::EqualFileSpec(currSpec, inFSSpec)) registerIt = false; } // If we haven't already stored this file... if(registerIt){ // If we're not allowed to add more items, // remove one item at head of list if(mAliasArray.GetCount() >= mMaxDocCount){ cDocAlias* firstItem = mAliasArray[LArray::index_First]; mAliasArray.Remove(firstItem); delete firstItem; } mAliasArray.AddItem( new cDocAlias(inFSSpec)); } } // ----------------------------------------------------------------- // Ä UnregisterFile(AliasHandle) // ----------------------------------------------------------------- // Remove a file from our alias list (if in it). void cDocListMenu:: UnregisterFile(AliasHandle inAliasHandle){ FSSpec theSpec; Boolean wasChanged = false; if(::ResolveAlias(&theSpec, inAliasHandle, NULL, &wasChanged) == noErr) UnregisterFile(theSpec); } // ----------------------------------------------------------------- // Ä UnregisterFile(LFile&) // ----------------------------------------------------------------- // Remove a file from our alias list (if in it). void cDocListMenu:: UnregisterFile(LFile& inFile){ FSSpec theSpec; inFile.GetSpecifier(theSpec); UnregisterFile(theSpec); } // ----------------------------------------------------------------- // Ä UnregisterFile(FSSpec&) // ----------------------------------------------------------------- // Remove a file from our alias list (if in it). void cDocListMenu:: UnregisterFile(FSSpec& inFSSpec){ FSSpec currSpec; ArrayIndexT foundIndex; Boolean foundIt = false; // Walk thru array, see if FSSpec is in any of the cDocAliases TArrayIterator iterator(mAliasArray, LArrayIterator::from_Start); cDocAlias* theAlias; while(iterator.Next(theAlias) && foundIt == false) { theAlias->GetFileSpec(currSpec); if(LFile::EqualFileSpec(currSpec, inFSSpec)){ foundIndex = iterator.GetCurrentIndex(); foundIt = true; } } if(foundIt){ cDocAlias* item = mAliasArray[foundIndex]; mAliasArray.Remove(item); delete item; } } // ----------------------------------------------------------------- // Ä HandleCommandStatus() // ----------------------------------------------------------------- // Set command status of items in our menu (if it is our menu). // Return whether host should execute (if it isn't our menu, host should execute). Boolean cDocListMenu:: HandleCommandStatus(SCommandStatus& ioCommandStatus){ Boolean result = true; ResIDT synthMenu; Int16 synthItem; if (ioCommandStatus.command == mMenuID || (LCommander::IsSyntheticCommand(ioCommandStatus.command, synthMenu, synthItem) && synthMenu == mMenuID)){ *ioCommandStatus.enabled = true; result = false; } return result; } // ----------------------------------------------------------------- // Ä HandleDocListMenuChoice() // ----------------------------------------------------------------- // Handle selection in our menu. Return whether host should execute. Boolean cDocListMenu:: HandleDocListMenuChoice(Int16 inMenuItem){ FSSpec theFile; Boolean result = true; // Try to make an FSSpec out of the menu selection if(GetSelectedFSSpec(inMenuItem, theFile) == noErr){ // If we got FSSpec, pass it on to HandleSelectedFSSpec() HandleSelectedFSSpec(theFile); result = false; } return result; } // ----------------------------------------------------------------- // Ä GetSelectedFSSpec() // ----------------------------------------------------------------- // Return FSSpec corresponding to menu item index. // If index is valid, returns error code from cDocAlias::GetFileSpec(). // Otherwise, returns -1. OSErr cDocListMenu:: GetSelectedFSSpec(Int16 inMenuItem, FSSpec& outFSSpec){ OSErr result = -1; ArrayIndexT index = inMenuItem; if(mAliasArray.ValidIndex(index)) result = mAliasArray[index]->GetFileSpec(outFSSpec); return result; } // ----------------------------------------------------------------- // Ä HandleSelectedFSSpec() // ----------------------------------------------------------------- // Checks if attachment host is derived from LDocApplication. // If so, calls host's SendAEOpenDoc() function with file spec passed in. void cDocListMenu:: HandleSelectedFSSpec(FSSpec& inFSSpec){ LDocApplication* docApp = dynamic_cast(mOwnerHost); if(docApp) docApp->SendAEOpenDoc(inFSSpec); } // ----------------------------------------------------------------- // Ä RebuildMenu() // ----------------------------------------------------------------- // Rebuild the doc menu from scratch. // We also check if the aliases are valid, and remove those that aren't. void cDocListMenu:: RebuildMenu(){ LMenuBar* currMBAR = NULL; LMenu* ourMenu = NULL; MenuHandle ourMenuH = NULL; Str255 docName = "\p"; Int16 mItems = 0; ArrayIndexT currIndex = 0; currMBAR = LMenuBar::GetCurrentMenuBar(); ThrowIfNil_(currMBAR); ourMenu = currMBAR->FetchMenu(mMenuID); ThrowIfNil_(ourMenu); ourMenuH = ourMenu->GetMacMenuH(); ThrowIfNil_(ourMenuH); // Empty menu mItems = ::CountMItems(ourMenuH); while ( mItems > 0 ) ourMenu->RemoveItem(mItems--); // Walk thru array, add names of docs to menu TArrayIterator iterator(mAliasArray); cDocAlias* theAlias; while(iterator.Next(theAlias)) { if(theAlias->GetFileName(docName) == noErr){ currIndex = iterator.GetCurrentIndex(); ourMenu->InsertCommand(docName, cmd_UseMenuItem, currIndex); // NOTE: Menu manager filters certain meta characters when inserting menu items. // For instance, the file name "myDoc (Copy)" results in the disabled menu item string "myDoc Copy)". // To get around this, we manually enable each new menu item and set its string again by calling // SetMenuItemText() on the menu handle as well. ::SetMenuItemText(ourMenuH, currIndex, docName); ::EnableItem(ourMenuH, currIndex); } // Delete entries with invalid aliases else{ mAliasArray.Remove(theAlias); delete theAlias; } } } // ----------------------------------------------------------------- // Ä DeleteAliasArrayItems() // ----------------------------------------------------------------- // Delete all cDocAlias objects whose ptrs are stored in mAliasArray void cDocListMenu:: DeleteAliasArrayItems(void){ TArrayIterator iterator(mAliasArray, LArray::index_First); cDocAlias* theAlias; while(iterator.Current(theAlias)) { mAliasArray.Remove(theAlias); delete theAlias; } } #pragma mark - cSavedDocListMenu* cSavedDocListMenu::sSavedDocListMenu = NULL; // ----------------------------------------------------------------- // Ä Constructor // ----------------------------------------------------------------- // Pass ID of MENU resource and number of recent docs to show in menu to cDocListMenu constructor. // Save file name of preferences file, and whether we should write to it when we're destroyed. cSavedDocListMenu::cSavedDocListMenu( ResIDT inMenuID, Uint32 inMaxDocCount, Str255 inPrefsFileName, Boolean inReadFromPrefsInConstructor, Boolean inWriteToPrefsInDestructor) : cDocListMenu(inMenuID, inMaxDocCount){ mPrefsFileName = inPrefsFileName; mWriteToPrefsInDestructor = inWriteToPrefsInDestructor; sSavedDocListMenu = this; if(inReadFromPrefsInConstructor) ReadFromPreferences(); } // ----------------------------------------------------------------- // Ä Destructor // ----------------------------------------------------------------- // Saves aliases to a resource in preferences file (if found). // Deletes cDocAlias objects in array of pointers. cSavedDocListMenu::~cSavedDocListMenu(){ if(mWriteToPrefsInDestructor) SaveToPreferences(); // If static pointer points to us, NULL it if(sSavedDocListMenu == this) sSavedDocListMenu = NULL; } // ----------------------------------------------------------------- // Ä SaveToPreferences() // ----------------------------------------------------------------- // Try to open preferences file (whose name was passed to constructor), // delete existing alias handle resource, create new resource, store handle from GetResourceData(). // May throw ResError()s resulting from AddResource() and UpdateResFile(). // Error handling might be better here. void cSavedDocListMenu:: SaveToPreferences(OSType inResType, ResIDT inResID){ Handle resHandle = NULL; Int16 prefsFileNum = OpenPrefsResFile(); if(prefsFileNum != -1){ try{ // Delete existing resource resHandle = ::Get1Resource(inResType, inResID); if(resHandle){ ::RemoveResource(resHandle); ::DisposeHandle(resHandle); } resHandle = GetResourceData(); // Add new resource ::AddResource(resHandle, inResType, inResID, "\p"); ThrowIfResError_(); ::ReleaseResource(resHandle); // Update & close res file ::UpdateResFile(prefsFileNum); ThrowIfResError_(); ::CloseResFile(prefsFileNum); } catch(...){ ::CloseResFile(prefsFileNum); throw; } } } // ----------------------------------------------------------------- // Ä ReadFromPreferences() // ----------------------------------------------------------------- // Try to open preferences file (whose name was passed to constructor), // get alias handle resource, pass its handle to SetResourceData(). void cSavedDocListMenu:: ReadFromPreferences(OSType inResType, ResIDT inResID){ Handle resHandle = NULL; Int16 prefsFileNum = OpenPrefsResFile(); if(prefsFileNum != -1){ resHandle = ::Get1Resource(inResType, inResID); if(resHandle){ ::DetachResource(resHandle); SetResourceData(resHandle); } ::CloseResFile(prefsFileNum); } } // ----------------------------------------------------------------- // Ä GetResourceData() // ----------------------------------------------------------------- // Return new handle with data to store in resource or elsewhere. // This data is may be passed to SetResourceData() to reconstruct object. // Caller is responsible for deallocating handle (which is NOT a resource handle). Handle cSavedDocListMenu:: GetResourceData(void){ Uint32 nAliases = mAliasArray.GetCount(); AliasHandle aliasData = NULL; LHandleStream hStream; // Store count of aliases hStream << nAliases; // Walk thru array, get AliasHandles & write them to stream TArrayIterator iterator(mAliasArray); cDocAlias* theAlias; while(iterator.Next(theAlias)) { aliasData = theAlias->GetAliasHandle(); hStream << (Handle&)aliasData; } return hStream.DetachDataHandle(); } // ----------------------------------------------------------------- // Ä SetResourceData() // ----------------------------------------------------------------- // Recreate alias list from resource data, as obtained by GetResourceData(). // adoptHandle indicates whether function should dispose of inResData (default value is true). // Note that if we're adopting handle, we're calling DisposeHandle() on it, // so it can't be a resource handle. void cSavedDocListMenu:: SetResourceData(Handle inResData, Boolean inAdoptHandle){ Uint32 nAliases = 0; AliasHandle aliasData = NULL; // Empty alias array DeleteAliasArrayItems(); if(!inAdoptHandle) ::HandToHand(&inResData); LHandleStream hStream(inResData); hStream >> nAliases; for(Uint32 i = 0; i < nAliases; i++){ hStream >> (Handle&)aliasData; RegisterFile(aliasData); ::DisposeHandle((Handle)aliasData); } } // ----------------------------------------------------------------- // Ä OpenPrefsResFile() // ----------------------------------------------------------------- // Find preferences file, try to open its resource fork. // Result is res fork file reference number or -1 if call failed. // Don't forget to close file. Int16 cSavedDocListMenu:: OpenPrefsResFile(void){ FSSpec prefsFileSpec; long prefsDirID = 0; short prefsVRefNum = 0; OSErr theErr = ::FindFolder(kOnSystemDisk, kPreferencesFolderType, true, &prefsVRefNum, &prefsDirID); if (theErr != noErr) return -1; ::FSMakeFSSpec(prefsVRefNum, prefsDirID, &mPrefsFileName[0], &prefsFileSpec); return ::FSpOpenResFile(&prefsFileSpec, fsRdWrPerm); } // ----------------------------------------------------------------- // Ä GetSavedDocListMenu() // ----------------------------------------------------------------- // Return a pointer to last created cSavedDocListMenu that's still around. // Will return a valid pointer or NULL. // Useful if you create one and only one "recent document menu" and need to access it from any place. // If you create several instances of a cSavedDocListMenu (or derived class), this returns pointer // to the one last created. cSavedDocListMenu* cSavedDocListMenu:: GetSavedDocListMenu(void){ return sSavedDocListMenu; } #pragma mark - // ----------------------------------------------------------------- // Ä cDirectoryDocListMenu() // ----------------------------------------------------------------- // Takes parameters of cDocListMenu plus a file type to scan for, a directory to scan, // a Boolean to tell if directory should be rescanned every time menu is rebuilt // (= every time ExecuteSelf() is called), and another Boolean to tell // if directory should be scanned in constructor. // If directory is rescanned when ExecuteSelf() is called, any files that have been added to directory // since cDirectoryDocListMenu was created appear in the menu, otherwise they don't. // Rescanning the directory may be very quick if there are only a couple of files in it, // but typically you won't want to rescan it. cDirectoryDocListMenu::cDirectoryDocListMenu( ResIDT inMenuID, Uint32 inMaxDocCount, OSType inFileType, FSSpec& inDirectory, Boolean inRescanDirectoryOnRebuild, Boolean inScanInConstructor) : cDocListMenu(inMenuID, inMaxDocCount){ mRescanDirectoryOnRebuild = inRescanDirectoryOnRebuild; mFileTypeToScanFor = inFileType; SetDirectory(inDirectory, inScanInConstructor); } // ----------------------------------------------------------------- // Ä cDirectoryDocListMenu() // ----------------------------------------------------------------- // Constructor that doesn't take directory FSSpec. Useful for subclasses. cDirectoryDocListMenu::cDirectoryDocListMenu( ResIDT inMenuID, Uint32 inMaxDocCount, OSType inFileType, Boolean inRescanDirectoryOnRebuild) : cDocListMenu(inMenuID, inMaxDocCount){ mDirectory.vRefNum = -1; mDirectory.parID = -1; mDirectory.name[0] = 0; mRescanDirectoryOnRebuild = inRescanDirectoryOnRebuild; mFileTypeToScanFor = inFileType; } // ----------------------------------------------------------------- // Ä SetDirectory() // ----------------------------------------------------------------- // Set mDirectory to inDirectory, rescan if caller wants to. void cDirectoryDocListMenu::SetDirectory(FSSpec& inDirectory, Boolean inRescanDirectory){ mDirectory = inDirectory; if(inRescanDirectory) ScanDirectoryItems(); } // ----------------------------------------------------------------- // Ä ScanDirectoryItems() // ----------------------------------------------------------------- // We empty our alias array, then scan directory specified in mDirectory FSSpec // for any files of type mFileTypeToScanFor, adding any hits to array. // Directory is scanned one level deep (so subfolders are not scanned). // We don't update the menu yet. Call RebuildMenu() to do that. // Adjust eFSSpecBufferSize enum in header to adjust search speed. More speed = larger stack use. // Uses GetDirItems() from Jim Luther's MoreFiles package. void cDirectoryDocListMenu::ScanDirectoryItems(void){ FSSpec items[eFSSpecBufferSize]; FInfo fileInfo; Int16 actItemCount = 1, itemIndex = 1; // Empty alias array DeleteAliasArrayItems(); // Traverse directory until mAliasArray is as full as we allow it to be, or we've traversed all the files while (actItemCount > 0 && mAliasArray.GetCount() < mMaxDocCount) { // GetDirItems() from Jim Luther's MoreFiles package. OSErr error = GetDirItems(mDirectory.vRefNum, mDirectory.parID, nil, true, false, items, eFSSpecBufferSize, &actItemCount, &itemIndex); for(int i = 0; i < actItemCount; i++){ OSErr fInfoErr = ::FSpGetFInfo(&items[i], &fileInfo); if(!fInfoErr && fileInfo.fdType == mFileTypeToScanFor) RegisterFile(items[i]); } if(error != noErr) actItemCount = 0; } } // ----------------------------------------------------------------- // Ä RebuildMenu() (Overridden from cDocListMenu) // ----------------------------------------------------------------- // If mRescanDirectoryOnRebuild is true, rescan directory before calling cDocListMenu::RebuildMenu(). void cDirectoryDocListMenu::RebuildMenu(void){ if(mRescanDirectoryOnRebuild) ScanDirectoryItems(); cDocListMenu::RebuildMenu(); } #pragma mark - // ----------------------------------------------------------------- // Ä cAppDirectoryDocListMenu() // ----------------------------------------------------------------- // Pass parameters to cDirectoryDocListMenu constructor, // then initialize mDirectory to application's FSSpec using the Process manager, // then call ScanDirectoryItems(). cAppDirectoryDocListMenu::cAppDirectoryDocListMenu( ResIDT inMenuID, Uint32 inMaxDocCount, OSType inFileType, Boolean inRescanDirectoryOnRebuild) : cDirectoryDocListMenu(inMenuID, inMaxDocCount, inFileType, inRescanDirectoryOnRebuild){ MakeAppFSSpec(mDirectory); ScanDirectoryItems(); } // ----------------------------------------------------------------- // Ä MakeAppFSSpec() // ----------------------------------------------------------------- // Initialize outSpec to application's FSSpec using the Process manager. void cAppDirectoryDocListMenu::MakeAppFSSpec(FSSpec& outSpec){ ProcessSerialNumber psn; ProcessInfoRec procInfoRec; ::GetCurrentProcess(&psn); procInfoRec.processInfoLength = sizeof(ProcessInfoRec); procInfoRec.processName = nil; procInfoRec.processAppSpec = &outSpec; ::GetProcessInformation (&psn, &procInfoRec); } #pragma mark - Boolean cInsertDocDirectoryDocListMenu::sRecursionSemaphore = false; // ----------------------------------------------------------------- // Ä cInsertDocDirectoryDocListMenu() // ----------------------------------------------------------------- // Pass parameters to cDirectoryDocListMenu constructor, // store command for inserting documents cInsertDocDirectoryDocListMenu::cInsertDocDirectoryDocListMenu( ResIDT inMenuID, Uint32 inMaxDocCount, OSType inFileType, FSSpec& inDirectory, CommandT inInsertDocumentCommand, Boolean inRescanDirectoryOnRebuild, Boolean inScanInConstructor) : cDirectoryDocListMenu(inMenuID, inMaxDocCount, inFileType, inDirectory, inRescanDirectoryOnRebuild, inScanInConstructor){ mInsertDocCommand = inInsertDocumentCommand; } // ----------------------------------------------------------------- // Ä cInsertDocDirectoryDocListMenu() // ----------------------------------------------------------------- // Pass parameters to cDirectoryDocListMenu constructor, // store command for inserting documents cInsertDocDirectoryDocListMenu::cInsertDocDirectoryDocListMenu( ResIDT inMenuID, Uint32 inMaxDocCount, OSType inFileType, CommandT inInsertDocumentCommand, Boolean inRescanDirectoryOnRebuild) : cDirectoryDocListMenu(inMenuID, inMaxDocCount, inFileType, inRescanDirectoryOnRebuild){ mInsertDocCommand = inInsertDocumentCommand; } // ----------------------------------------------------------------- // Ä HandleCommandStatus() (Overridden from cDocListMenu) // ----------------------------------------------------------------- // If we have a front document, call its FindCommandStatus() function. // If not, disable menu. Boolean cInsertDocDirectoryDocListMenu:: HandleCommandStatus(SCommandStatus& ioCommandStatus){ Boolean result = true; ResIDT synthMenu; Int16 synthItem; if (ioCommandStatus.command == mMenuID || (LCommander::IsSyntheticCommand( ioCommandStatus.command, synthMenu, synthItem) && synthMenu == mMenuID)){ LDocument* frontDocument = GetFrontDocument(); if(frontDocument && sRecursionSemaphore == false){ // Document may not handle mInsertDocCommand at all. If so, we will eventually // reach the top commander (the application), whose FindCommandStatus() will cause this // attachment to execute again (creating an infinite loop). // To get around this, use a semaphore to indicate if we're recursing. sRecursionSemaphore = true; frontDocument->FindCommandStatus(mInsertDocCommand, *ioCommandStatus.enabled, *ioCommandStatus.usesMark, *ioCommandStatus.mark, ioCommandStatus.name); sRecursionSemaphore = false; } // If we're recursing, no one handles the mInsertDocCommand, so disable it. else *ioCommandStatus.enabled = false; result = false; } return result; } // ----------------------------------------------------------------- // Ä HandleSelectedFSSpec() (Overridden from cDocListMenu) // ----------------------------------------------------------------- // We try to get the front document, and if we find any, // call its ObeyCommand() function with mInsertDocCommand and selected FSSpec void cInsertDocDirectoryDocListMenu::HandleSelectedFSSpec(FSSpec& inFSSpec){ if(mInsertDocCommand != cmd_Nothing){ LDocument* frontDocument = GetFrontDocument(); if(frontDocument) frontDocument->ObeyCommand(mInsertDocCommand, &inFSSpec); } } // ----------------------------------------------------------------- // Ä GetFrontDocument() // ----------------------------------------------------------------- // Get the top regular window and traverse its LCommander chain until we find // a commander we can cast to LDocument, or until we reach top of command chain. // If top regular window doesn't belong to a document, we return NULL, // likewise if top floater should happen to belong to a document. LDocument* cInsertDocDirectoryDocListMenu::GetFrontDocument(void){ LDocument* result = NULL; LWindow* topRegular = UDesktop::FetchTopRegular(); LCommander* currCmdr = dynamic_cast(topRegular); while(currCmdr && !result){ result = dynamic_cast(currCmdr); if(!result) currCmdr = currCmdr->GetSuperCommander(); } return result; } #pragma mark - // ----------------------------------------------------------------- // Ä cInsertAppDirDocMenu() // ----------------------------------------------------------------- // Same as cInsertDocDirectoryDocListMenu, except we scan the application file's directory. cInsertAppDirDocMenu::cInsertAppDirDocMenu( ResIDT inMenuID, Uint32 inMaxDocCount, OSType inFileType, CommandT inInsertDocumentCommand, Boolean inRescanDirectoryOnRebuild) : cInsertDocDirectoryDocListMenu(inMenuID, inMaxDocCount, inFileType, inInsertDocumentCommand, inRescanDirectoryOnRebuild){ cAppDirectoryDocListMenu::MakeAppFSSpec(mDirectory); ScanDirectoryItems(); } #pragma mark - // ----------------------------------------------------------------- // Ä cInsertAppDirDocMenuWithChoice() // ----------------------------------------------------------------- // Same as cInsertAppDirDocMenu, except we always keep two items at bottom of menu: // A separator, and below it a string which causes us to display a Standard file dialog // for choosing the file to insert. We save off the two last items in the menu and restore it in RebuildMenu(). cInsertAppDirDocMenuWithChoice::cInsertAppDirDocMenuWithChoice( ResIDT inMenuID, Uint32 inMaxDocCount, OSType inFileType, CommandT inInsertDocumentCommand, Boolean inRescanDirectoryOnRebuild) : cInsertAppDirDocMenu(inMenuID, inMaxDocCount, inFileType, inInsertDocumentCommand, inRescanDirectoryOnRebuild){} // ----------------------------------------------------------------- // Ä RebuildMenu() (Overridden from cDocListMenu) // ----------------------------------------------------------------- // Save off last menu item string, call cDocListMenu::RebuildMenu(), // then add a separator and restore last item string void cInsertAppDirDocMenuWithChoice::RebuildMenu(void){ LMenuBar* currMBAR = NULL; LMenu* ourMenu = NULL; MenuHandle ourMenuH = NULL; Str255 lastStr; Int16 mItems = 0; FSSpec fileSpec; currMBAR = LMenuBar::GetCurrentMenuBar(); ThrowIfNil_(currMBAR); ourMenu = currMBAR->FetchMenu(mMenuID); ThrowIfNil_(ourMenu); ourMenuH = ourMenu->GetMacMenuH(); ThrowIfNil_(ourMenuH); ::GetMenuItemText(ourMenuH, ::CountMItems(ourMenuH), lastStr); cDocListMenu::RebuildMenu(); ::InsertMenuItem(ourMenuH, "\p-", ::CountMItems(ourMenuH)); ::InsertMenuItem(ourMenuH, lastStr, CountMItems(ourMenuH)); } // ----------------------------------------------------------------- // Ä HandleDocListMenuChoice() (Overridden from cDocListMenu) // ----------------------------------------------------------------- // If user selected last item in menu, run a file dialog to get a file to insert. Boolean cInsertAppDirDocMenuWithChoice::HandleDocListMenuChoice(Int16 inMenuItem){ LMenuBar* currMBAR = NULL; LMenu* ourMenu = NULL; MenuHandle ourMenuH = NULL; FSSpec fileSpec; Boolean result = true; currMBAR = LMenuBar::GetCurrentMenuBar(); ThrowIfNil_(currMBAR); ourMenu = currMBAR->FetchMenu(mMenuID); ThrowIfNil_(ourMenu); ourMenuH = ourMenu->GetMacMenuH(); ThrowIfNil_(ourMenuH); // If last menu item was selected if(inMenuItem == ::CountMItems(ourMenuH)){ // If user didn't cancel file dialog if(DoStandardFile(fileSpec)){ HandleSelectedFSSpec(fileSpec); } result = true; } // If item before last item was selected, call inherited else result = cInsertAppDirDocMenu::HandleDocListMenuChoice(inMenuItem); return result; } // ----------------------------------------------------------------- // Ä DoStandardFile() // ----------------------------------------------------------------- // Run a file dialog with mFileTypeToScanFor as file type. // If successful, put FSSpec into outSpec and return true; otherwise, return false. Boolean cInsertAppDirDocMenuWithChoice::DoStandardFile(FSSpec& outSpec){ Boolean result = false; // Deactivate the desktop. StDesktopDeactivator deactivatedDesktop; // Browse for a document. SFTypeList theTypeList = { mFileTypeToScanFor }; StandardFileReply theReply; ::StandardGetFile( nil, 1, theTypeList, &theReply ); outSpec = theReply.sfFile; result = theReply.sfGood; return result; }