X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e9576ca53db96b462ed4c0b4bdf47d64c40203e4..0b014ec713bbadb49460e4b23310fd325cdc0036:/src/mac/dirdlg.cpp diff --git a/src/mac/dirdlg.cpp b/src/mac/dirdlg.cpp index 095621b978..e1255db7e1 100644 --- a/src/mac/dirdlg.cpp +++ b/src/mac/dirdlg.cpp @@ -20,10 +20,429 @@ #include "wx/cmndata.h" +#if defined(__UNIX__) + #include +#else + #include +#endif + #if !USE_SHARED_LIBRARY IMPLEMENT_CLASS(wxDirDialog, wxDialog) #endif +bool gUseNavServices = NavServicesAvailable() ; + +// the data we need to pass to our standard file hook routine +// includes a pointer to the dialog, a pointer to the standard +// file reply record (so we can inspect the current selection) +// and a copy of the "previous" file spec of the reply record +// so we can see if the selection has changed + +#if !TARGET_CARBON + +struct UserDataRec { + StandardFileReply *sfrPtr; + FSSpec oldSelectionFSSpec; + DialogPtr theDlgPtr; +}; +typedef struct UserDataRec + UserDataRec, *UserDataRecPtr; + +enum { + kSelectItem = 10, // select button item number + kSFGetFolderDlgID = 250, // dialog resource number + kStrListID = 250, // our strings + kSelectStrNum = 1, // word 'Select: ' for button + kDesktopStrNum = 2, // word 'Desktop' for button + kSelectNoQuoteStrNum = 3, // word 'Select: ' for button + + kUseQuotes = true, // parameter for SetButtonName + kDontUseQuotes = false +}; + + +static void GetLabelString(StringPtr theStr, short stringNum) +{ + GetIndString(theStr, kStrListID, stringNum); +} + +static void CopyPStr(StringPtr src, StringPtr dest) +{ + BlockMoveData(src, dest, 1 + src[0]); +} + +static char GetSelectKey(void) +{ + // this is the key used to trigger the select button + + // NOT INTERNATIONAL SAVVY; should at least grab it from resources + + return 's'; +} + + +// SetButtonName sets the name of the Select button in the dialog +// +// To do this, we need to call the Script Manager to truncate the +// label in the middle to fit the button and to merge the button +// name with the word Select (possibly followed by quotes). Using +// the Script Manager avoids all sorts of problems internationally. +// +// buttonName is the name to appear following the word Select +// quoteFlag should be true if the name is to appear in quotes + +static void SetButtonName(DialogPtr theDlgPtr, short buttonID, StringPtr buttonName, + Boolean quoteFlag) +{ + short buttonType; + Handle buttonHandle; + Rect buttonRect; + short textWidth; + Handle labelHandle; + Handle nameHandle; + Str15 keyStr; + Str255 labelStr; + OSErr err; + + nameHandle = nil; + labelHandle = nil; + + // get the details of the button from the dialog + + GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect); + + // get the string for the select button label, "Select ^0" or "Select Ò^0Ó" + + GetLabelString(labelStr, (quoteFlag == kUseQuotes) ? kSelectStrNum : kSelectNoQuoteStrNum); + + // make string handles containing the select button label and the + // file name to be stuffed into the button + + err = PtrToHand(&labelStr[1], &labelHandle, labelStr[0]); + if (err != noErr) goto Bail; + + // cut out the middle of the file name to fit the button + // + // we'll temporarily use labelStr here to hold the modified button name + // since we don't own the buttonName string storage space + + textWidth = (buttonRect.right - buttonRect.left) - StringWidth(labelStr); + + CopyPStr(buttonName, labelStr); + (void) TruncString(textWidth, labelStr, smTruncMiddle); + + err = PtrToHand(&labelStr[1], &nameHandle, labelStr[0]); + if (err != noErr) goto Bail; + + // replace the ^0 in the Select string with the file name + + CopyPStr("\p^0", keyStr); + + (void) ReplaceText(labelHandle, nameHandle, keyStr); + + labelStr[0] = (unsigned char) GetHandleSize(labelHandle); + BlockMoveData(*labelHandle, &labelStr[1], labelStr[0]); + + // now set the control title, and re-validate the area + // above the control to avoid a needless redraw + + SetControlTitle((ControlHandle) buttonHandle, labelStr); + + ValidRect(&buttonRect); + +Bail: + if (nameHandle) DisposeHandle(nameHandle); + if (labelHandle) DisposeHandle(labelHandle); + +} + +// FlashButton briefly highlights the dialog button +// as feedback for key equivalents + +static void FlashButton(DialogPtr theDlgPtr, short buttonID) +{ + short buttonType; + Handle buttonHandle; + Rect buttonRect; + unsigned long finalTicks; + + GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect); + HiliteControl((ControlHandle) buttonHandle, kControlButtonPart); + Delay(10, &finalTicks); + HiliteControl((ControlHandle) buttonHandle, 0); +} + +static Boolean SameFSSpec(FSSpecPtr spec1, FSSpecPtr spec2) +{ + return (spec1->vRefNum == spec2->vRefNum + && spec1->parID == spec2->parID + && EqualString(spec1->name, spec2->name, false, false)); +} +// MyModalDialogFilter maps a key to the Select button, and handles +// flashing of the button when the key is hit + +static pascal Boolean SFGetFolderModalDialogFilter(DialogPtr theDlgPtr, EventRecord *eventRec, + short *item, Ptr dataPtr) +{ +#pragma unused (dataPtr) + + // make certain the proper dialog is showing, 'cause standard file + // can nest dialogs but calls the same filter for each + + if (((WindowPeek) theDlgPtr)->refCon == sfMainDialogRefCon) + { + // check if the select button was hit + + if ((eventRec->what == keyDown) + && (eventRec->modifiers & cmdKey) + && ((eventRec->message & charCodeMask) == GetSelectKey())) + { + *item = kSelectItem; + FlashButton(theDlgPtr, kSelectItem); + return true; + } + } + + return false; +} + + +// MyDlgHook is a hook routine that maps the select button to Open +// and sets the Select button name + +static pascal short SFGetFolderDialogHook(short item, DialogPtr theDlgPtr, Ptr dataPtr) +{ + UserDataRecPtr theUserDataRecPtr; + long desktopDirID; + short desktopVRefNum; + FSSpec tempSpec; + Str63 desktopName; + OSErr err; + + // be sure Std File is really showing us the intended dialog, + // not a nested modal dialog + + if (((WindowPeek) theDlgPtr)->refCon != sfMainDialogRefCon) + { + return item; + } + + theUserDataRecPtr = (UserDataRecPtr) dataPtr; + + // map the Select button to Open + + if (item == kSelectItem) + { + item = sfItemOpenButton; + } + + // find the desktop folder + + err = FindFolder(theUserDataRecPtr->sfrPtr->sfFile.vRefNum, + kDesktopFolderType, kDontCreateFolder, + &desktopVRefNum, &desktopDirID); + + if (err != noErr) + { + // for errors, get value that won't match any real vRefNum/dirID + desktopVRefNum = 0; + desktopDirID = 0; + } + + // change the Select button label if the selection has changed or + // if this is the first call to the hook + + if (item == sfHookFirstCall + || item == sfHookChangeSelection + || item == sfHookRebuildList + || ! SameFSSpec(&theUserDataRecPtr->sfrPtr->sfFile, + &theUserDataRecPtr->oldSelectionFSSpec)) + { + // be sure there is a file name selected + + if (theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0') + { + SetButtonName(theDlgPtr, kSelectItem, + theUserDataRecPtr->sfrPtr->sfFile.name, + kUseQuotes); // true -> use quotes + } + else + { + // is the desktop selected? + + if (theUserDataRecPtr->sfrPtr->sfFile.vRefNum == desktopVRefNum + && theUserDataRecPtr->sfrPtr->sfFile.parID == desktopDirID) + { + // set button to "Select Desktop" + + GetLabelString(desktopName, kDesktopStrNum); + SetButtonName(theDlgPtr, kSelectItem, + desktopName, kDontUseQuotes); // false -> no quotes + } + else + { + // get parent directory's name for the Select button + // + // passing an empty name string to FSMakeFSSpec gets the + // name of the folder specified by the parID parameter + + (void) FSMakeFSSpec(theUserDataRecPtr->sfrPtr->sfFile.vRefNum, + theUserDataRecPtr->sfrPtr->sfFile.parID, "\p", + &tempSpec); + SetButtonName(theDlgPtr, kSelectItem, + tempSpec.name, kUseQuotes); // true -> use quotes + } + } + } + + // save the current selection as the old selection for comparison next time + // + // it's not valid on the first call, though, or if we don't have a + // name available from standard file + + if (item != sfHookFirstCall || theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0') + { + theUserDataRecPtr->oldSelectionFSSpec = theUserDataRecPtr->sfrPtr->sfFile; + } + else + { + // on first call, empty string won't set the button correctly, + // so invalidate oldSelection + + theUserDataRecPtr->oldSelectionFSSpec.vRefNum = 999; + theUserDataRecPtr->oldSelectionFSSpec.parID = 0; + } + + return item; +} + +void StandardGetFolder( ConstStr255Param message , ConstStr255Param path , FileFilterYDUPP fileFilter, StandardFileReply *theSFR) +{ + Point thePt; + SFTypeList mySFTypeList; + UserDataRec myData; + FSSpec tempSpec; + Boolean folderFlag; + Boolean wasAliasedFlag; + DlgHookYDUPP dlgHookUPP; + ModalFilterYDUPP myModalFilterUPP; + OSErr err; + + + // presumably we're running System 7 or later so CustomGetFile is + // available + + // set initial contents of Select button to a space + + memcpy(theSFR->sfFile.name, "\p ", 2); + + // point the user data parameter at the reply record so we can get to it later + + myData.sfrPtr = theSFR; + + // display the dialog + + #if !TARGET_CARBON + + dlgHookUPP = NewDlgHookYDProc(SFGetFolderDialogHook); + myModalFilterUPP = NewModalFilterYDProc(SFGetFolderModalDialogFilter); + + thePt.h = thePt.v = -1; // center dialog + + ParamText( message , NULL , NULL , NULL ) ; + + CustomGetFile( fileFilter, + -1, // show all types + mySFTypeList, + theSFR, + kSFGetFolderDlgID, + thePt, // top left point + dlgHookUPP, + myModalFilterUPP, + nil, // activate list + nil, // activate proc + &myData); + + DisposeRoutineDescriptor(dlgHookUPP); + DisposeRoutineDescriptor(myModalFilterUPP); + #else + #endif + + // if cancel wasn't pressed and no fatal error occurred... + + if (theSFR->sfGood) + { + // if no name is in the reply record file spec, + // use the file spec of the parent folder + + if (theSFR->sfFile.name[0] == '\0') + { + err = FSMakeFSSpec(theSFR->sfFile.vRefNum, theSFR->sfFile.parID, + "\p", &tempSpec); + if (err == noErr) + { + theSFR->sfFile = tempSpec; + } + else + { + // no name to return, forget it + + theSFR->sfGood = false; + } + } + + // if there is now a name in the file spec, check if it's + // for a folder or a volume + + if (theSFR->sfFile.name[0] != '\0') + { + // the parID of the root of a disk is always fsRtParID == 1 + + if (theSFR->sfFile.parID == fsRtParID) + { + theSFR->sfIsVolume = true; + theSFR->sfIsFolder = false; // it would be reasonable for this to be true, too + } + + // we have a valid FSSpec, now let's make sure it's not for an alias file + + err = ResolveAliasFile(&theSFR->sfFile, true, &folderFlag, &wasAliasedFlag); + if (err != noErr) + { + theSFR->sfGood = false; + } + + // did the alias resolve to a folder? + + if (folderFlag && ! theSFR->sfIsVolume) + { + theSFR->sfIsFolder = true; + } + } + } +} + +static pascal Boolean OnlyVisibleFoldersCustomFileFilter(CInfoPBPtr myCInfoPBPtr, Ptr dataPtr) +{ +#pragma unused (dataPtr) + + // return true if this item is invisible or a file + + Boolean visibleFlag; + Boolean folderFlag; + + visibleFlag = ! (myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible); + folderFlag = (myCInfoPBPtr->hFileInfo.ioFlAttrib & 0x10); + + // because the semantics of the filter proc are "true means don't show + // it" we need to invert the result that we return + + return !(visibleFlag && folderFlag); +} + +#endif + + wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message, const wxString& defaultPath, long style, const wxPoint& pos) @@ -36,7 +455,145 @@ wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message, int wxDirDialog::ShowModal() { - // TODO - return wxID_CANCEL; + #if !TARGET_CARBON + if ( !gUseNavServices ) + { + Str255 prompt ; + Str255 path ; + +#if TARGET_CARBON + c2pstrcpy((StringPtr)prompt, m_message) ; +#else + strcpy((char *)prompt, m_message) ; + c2pstr((char *)prompt ) ; +#endif +#if TARGET_CARBON + c2pstrcpy((StringPtr)path, m_path ) ; +#else + strcpy((char *)path, m_path ) ; + c2pstr((char *)path ) ; +#endif + + StandardFileReply reply ; + FileFilterYDUPP invisiblesExcludedCustomFilterUPP = 0 ; + invisiblesExcludedCustomFilterUPP = + NewFileFilterYDProc(OnlyVisibleFoldersCustomFileFilter); + + + StandardGetFolder( prompt , path , invisiblesExcludedCustomFilterUPP, &reply); + + + DisposeRoutineDescriptor(invisiblesExcludedCustomFilterUPP); + + if ( reply.sfGood == false ) + { + m_path = "" ; + return wxID_CANCEL ; + } + else + { + m_path = wxMacFSSpec2UnixFilename( &reply.sfFile ) ; + return wxID_OK ; + } + return wxID_CANCEL; + } + else + #endif + { + NavDialogOptions mNavOptions; + NavObjectFilterUPP mNavFilterUPP = NULL; + NavPreviewUPP mNavPreviewUPP = NULL ; + NavReplyRecord mNavReply; + AEDesc* mDefaultLocation = NULL ; + bool mSelectDefault = false ; + + ::NavGetDefaultDialogOptions(&mNavOptions); + + mNavFilterUPP = nil; + mNavPreviewUPP = nil; + mSelectDefault = false; + mNavReply.validRecord = false; + mNavReply.replacing = false; + mNavReply.isStationery = false; + mNavReply.translationNeeded = false; + mNavReply.selection.descriptorType = typeNull; + mNavReply.selection.dataHandle = nil; + mNavReply.keyScript = smSystemScript; + mNavReply.fileTranslation = nil; + + // Set default location, the location + // that's displayed when the dialog + // first appears + + if ( mDefaultLocation ) { + + if (mSelectDefault) { + mNavOptions.dialogOptionFlags |= kNavSelectDefaultLocation; + } else { + mNavOptions.dialogOptionFlags &= ~kNavSelectDefaultLocation; + } + } + + OSErr err = ::NavChooseFolder( + mDefaultLocation, + &mNavReply, + &mNavOptions, + NULL, + mNavFilterUPP, + 0L); // User Data + + if ( (err != noErr) && (err != userCanceledErr) ) { + m_path = "" ; + return wxID_CANCEL ; + } + + if (mNavReply.validRecord) { // User chose a folder + + FSSpec folderInfo; + FSSpec outFileSpec ; + AEDesc specDesc ; + + OSErr err = ::AECoerceDesc( &mNavReply.selection , typeFSS, &specDesc); + if ( err != noErr ) { + m_path = "" ; + return wxID_CANCEL ; + } + folderInfo = **(FSSpec**) specDesc.dataHandle; + if (specDesc.dataHandle != nil) { + ::AEDisposeDesc(&specDesc); + } + +// mNavReply.GetFileSpec(folderInfo); + + // The FSSpec from NavChooseFolder is NOT the file spec + // for the folder. The parID field is actually the DirID + // of the folder itself, not the folder's parent, and + // the name field is empty. We must call PBGetCatInfo + // to get the parent DirID and folder name + + Str255 name; + CInfoPBRec thePB; // Directory Info Parameter Block + thePB.dirInfo.ioCompletion = nil; + thePB.dirInfo.ioVRefNum = folderInfo.vRefNum; // Volume is right + thePB.dirInfo.ioDrDirID = folderInfo.parID; // Folder's DirID + thePB.dirInfo.ioNamePtr = name; + thePB.dirInfo.ioFDirIndex = -1; // Lookup using Volume and DirID + + err = ::PBGetCatInfoSync(&thePB); + if ( err != noErr ) { + m_path = "" ; + return wxID_CANCEL ; + } + // Create cannonical FSSpec + ::FSMakeFSSpec(thePB.dirInfo.ioVRefNum, thePB.dirInfo.ioDrParID, + name, &outFileSpec); + + // outFolderDirID = thePB.dirInfo.ioDrDirID; + m_path = wxMacFSSpec2UnixFilename( &outFileSpec ) ; + return wxID_OK ; + } + return wxID_CANCEL; + + } }