1 ///////////////////////////////////////////////////////////////////////////// 
   3 // Purpose:     wxDirDialog 
   8 // Copyright:   (c) AUTHOR 
   9 // Licence:     wxWindows licence 
  10 ///////////////////////////////////////////////////////////////////////////// 
  13 #pragma implementation "dirdlg.h" 
  18 #include "wx/dialog.h" 
  19 #include "wx/dirdlg.h" 
  21 #include "wx/cmndata.h" 
  24   #include <NavigationServices/Navigation.h> 
  26   #include <Navigation.h> 
  29 #if !USE_SHARED_LIBRARY 
  30 IMPLEMENT_CLASS(wxDirDialog
, wxDialog
) 
  33 bool gUseNavServices 
= NavServicesAvailable() ; 
  35 // the data we need to pass to our standard file hook routine 
  36 // includes a pointer to the dialog, a pointer to the standard 
  37 // file reply record (so we can inspect the current selection) 
  38 // and a copy of the "previous" file spec of the reply record 
  39 // so we can see if the selection has changed 
  44         StandardFileReply       
*sfrPtr
; 
  45         FSSpec                          oldSelectionFSSpec
; 
  48 typedef struct UserDataRec
 
  49         UserDataRec
, *UserDataRecPtr
; 
  52         kSelectItem 
= 10,                       // select button item number 
  53         kSFGetFolderDlgID 
= 250,        // dialog resource number 
  54         kStrListID 
= 250,                       // our strings 
  55         kSelectStrNum 
= 1,                      // word 'Select: ' for button 
  56         kDesktopStrNum 
= 2,                     // word 'Desktop' for button 
  57         kSelectNoQuoteStrNum 
= 3,       // word 'Select: ' for button 
  59         kUseQuotes 
= true,                      // parameter for SetButtonName 
  60         kDontUseQuotes 
= false 
  64 static void GetLabelString(StringPtr theStr
, short stringNum
) 
  66         GetIndString(theStr
, kStrListID
, stringNum
); 
  69 static void CopyPStr(StringPtr src
, StringPtr dest
) 
  71         BlockMoveData(src
, dest
, 1 + src
[0]); 
  74 static char GetSelectKey(void) 
  76         // this is the key used to trigger the select button 
  78         // NOT INTERNATIONAL SAVVY; should at least grab it from resources 
  84 // SetButtonName sets the name of the Select button in the dialog 
  86 // To do this, we need to call the Script Manager to truncate the 
  87 // label in the middle to fit the button and to merge the button 
  88 // name with the word Select (possibly followed by quotes).  Using 
  89 // the Script Manager avoids all sorts of problems internationally. 
  91 // buttonName is the name to appear following the word Select 
  92 // quoteFlag should be true if the name is to appear in quotes 
  94 static void SetButtonName(DialogPtr theDlgPtr
, short buttonID
, StringPtr buttonName
, 
 110         // get the details of the button from the dialog 
 112         GetDialogItem(theDlgPtr
, buttonID
, &buttonType
, &buttonHandle
, &buttonRect
); 
 114         // get the string for the select button label, "Select ^0" or "Select Ò^0Ó" 
 116         GetLabelString(labelStr
, (quoteFlag 
== kUseQuotes
) ? kSelectStrNum 
: kSelectNoQuoteStrNum
); 
 118         // make string handles containing the select button label and the 
 119         // file name to be stuffed into the button 
 121         err 
= PtrToHand(&labelStr
[1], &labelHandle
, labelStr
[0]); 
 122         if (err 
!= noErr
) goto Bail
; 
 124         // cut out the middle of the file name to fit the button 
 126         // we'll temporarily use labelStr here to hold the modified button name 
 127         // since we don't own the buttonName string storage space 
 129         textWidth 
= (buttonRect
.right 
- buttonRect
.left
) - StringWidth(labelStr
); 
 131         CopyPStr(buttonName
, labelStr
); 
 132         (void) TruncString(textWidth
, labelStr
, smTruncMiddle
); 
 134         err 
= PtrToHand(&labelStr
[1], &nameHandle
, labelStr
[0]); 
 135         if (err 
!= noErr
) goto Bail
; 
 137         // replace the ^0 in the Select string with the file name 
 139         CopyPStr("\p^0", keyStr
); 
 141         (void) ReplaceText(labelHandle
, nameHandle
, keyStr
); 
 143         labelStr
[0] = (unsigned char) GetHandleSize(labelHandle
); 
 144         BlockMoveData(*labelHandle
, &labelStr
[1], labelStr
[0]); 
 146         // now set the control title, and re-validate the area 
 147         // above the control to avoid a needless redraw 
 149         SetControlTitle((ControlHandle
) buttonHandle
, labelStr
); 
 151         ValidRect(&buttonRect
); 
 154         if (nameHandle
)         DisposeHandle(nameHandle
); 
 155         if (labelHandle
)        DisposeHandle(labelHandle
); 
 159 // FlashButton briefly highlights the dialog button  
 160 // as feedback for key equivalents 
 162 static void FlashButton(DialogPtr theDlgPtr
, short buttonID
) 
 167         unsigned long   finalTicks
; 
 169         GetDialogItem(theDlgPtr
, buttonID
, &buttonType
, &buttonHandle
, &buttonRect
); 
 170         HiliteControl((ControlHandle
) buttonHandle
, kControlButtonPart
); 
 171         Delay(10, &finalTicks
); 
 172         HiliteControl((ControlHandle
) buttonHandle
, 0); 
 175 static Boolean 
SameFSSpec(FSSpecPtr spec1
, FSSpecPtr spec2
) 
 177         return (spec1
->vRefNum 
== spec2
->vRefNum
 
 178                         && spec1
->parID 
== spec2
->parID
 
 179                         && EqualString(spec1
->name
, spec2
->name
, false, false)); 
 181 // MyModalDialogFilter maps a key to the Select button, and handles 
 182 // flashing of the button when the key is hit 
 184 static pascal Boolean 
SFGetFolderModalDialogFilter(DialogPtr theDlgPtr
, EventRecord 
*eventRec
, 
 185                                                                                         short *item
, Ptr dataPtr
) 
 187 #pragma unused (dataPtr) 
 189         // make certain the proper dialog is showing, 'cause standard file 
 190         // can nest dialogs but calls the same filter for each 
 192         if (((WindowPeek
) theDlgPtr
)->refCon 
== sfMainDialogRefCon
) 
 194                 // check if the select button was hit 
 196                 if ((eventRec
->what 
== keyDown
) 
 197                         && (eventRec
->modifiers 
& cmdKey
)  
 198                         && ((eventRec
->message 
& charCodeMask
) == GetSelectKey())) 
 201                         FlashButton(theDlgPtr
, kSelectItem
); 
 210 // MyDlgHook is a hook routine that maps the select button to Open 
 211 // and sets the Select button name 
 213 static pascal short SFGetFolderDialogHook(short item
, DialogPtr theDlgPtr
, Ptr dataPtr
) 
 215         UserDataRecPtr  theUserDataRecPtr
; 
 217         short                   desktopVRefNum
; 
 222         // be sure Std File is really showing us the intended dialog, 
 223         // not a nested modal dialog 
 225         if (((WindowPeek
) theDlgPtr
)->refCon 
!= sfMainDialogRefCon
) 
 230         theUserDataRecPtr 
= (UserDataRecPtr
) dataPtr
; 
 232         // map the Select button to Open 
 234         if (item 
== kSelectItem
) 
 236                 item 
= sfItemOpenButton
; 
 239         // find the desktop folder 
 241         err 
= FindFolder(theUserDataRecPtr
->sfrPtr
->sfFile
.vRefNum
, 
 242                                         kDesktopFolderType
, kDontCreateFolder
, 
 243                                         &desktopVRefNum
, &desktopDirID
); 
 247                 // for errors, get value that won't match any real vRefNum/dirID 
 252         // change the Select button label if the selection has changed or 
 253         // if this is the first call to the hook 
 255         if (item 
== sfHookFirstCall
 
 256                 || item 
== sfHookChangeSelection
 
 257                 || item 
== sfHookRebuildList
 
 258                 || ! SameFSSpec(&theUserDataRecPtr
->sfrPtr
->sfFile
, 
 259                                         &theUserDataRecPtr
->oldSelectionFSSpec
)) 
 261                 // be sure there is a file name selected 
 263                 if (theUserDataRecPtr
->sfrPtr
->sfFile
.name
[0] != '\0') 
 265                         SetButtonName(theDlgPtr
, kSelectItem
,  
 266                                                         theUserDataRecPtr
->sfrPtr
->sfFile
.name
,  
 267                                                         kUseQuotes
);    // true -> use quotes 
 271                         // is the desktop selected? 
 273                         if (theUserDataRecPtr
->sfrPtr
->sfFile
.vRefNum 
== desktopVRefNum
 
 274                                 && theUserDataRecPtr
->sfrPtr
->sfFile
.parID 
== desktopDirID
) 
 276                                 // set button to "Select Desktop" 
 278                                 GetLabelString(desktopName
, kDesktopStrNum
); 
 279                                 SetButtonName(theDlgPtr
, kSelectItem
,  
 280                                                                 desktopName
, kDontUseQuotes
);   // false -> no quotes 
 284                                 // get parent directory's name for the Select button 
 286                                 // passing an empty name string to FSMakeFSSpec gets the 
 287                                 // name of the folder specified by the parID parameter 
 289                                 (void) FSMakeFSSpec(theUserDataRecPtr
->sfrPtr
->sfFile
.vRefNum
, 
 290                                         theUserDataRecPtr
->sfrPtr
->sfFile
.parID
, "\p", 
 292                                 SetButtonName(theDlgPtr
, kSelectItem
,  
 293                                                         tempSpec
.name
, kUseQuotes
); // true -> use quotes 
 298         // save the current selection as the old selection for comparison next time 
 300         // it's not valid on the first call, though, or if we don't have a  
 301         // name available from standard file 
 303         if (item 
!= sfHookFirstCall 
|| theUserDataRecPtr
->sfrPtr
->sfFile
.name
[0] != '\0') 
 305                 theUserDataRecPtr
->oldSelectionFSSpec 
= theUserDataRecPtr
->sfrPtr
->sfFile
; 
 309                 // on first call, empty string won't set the button correctly,  
 310                 // so invalidate oldSelection 
 312                 theUserDataRecPtr
->oldSelectionFSSpec
.vRefNum 
= 999; 
 313                 theUserDataRecPtr
->oldSelectionFSSpec
.parID 
= 0; 
 319 void StandardGetFolder( ConstStr255Param message 
, ConstStr255Param path 
, FileFilterYDUPP fileFilter
, StandardFileReply 
*theSFR
) 
 322         SFTypeList                      mySFTypeList
; 
 326         Boolean                         wasAliasedFlag
; 
 327         DlgHookYDUPP            dlgHookUPP
; 
 328         ModalFilterYDUPP        myModalFilterUPP
; 
 332         // presumably we're running System 7 or later so CustomGetFile is 
 335         // set initial contents of Select button to a space 
 337         memcpy(theSFR
->sfFile
.name
, "\p ", 2); 
 339         // point the user data parameter at the reply record so we can get to it later 
 341         myData
.sfrPtr 
= theSFR
; 
 343         // display the dialog 
 347         dlgHookUPP 
= NewDlgHookYDProc(SFGetFolderDialogHook
); 
 348         myModalFilterUPP 
= NewModalFilterYDProc(SFGetFolderModalDialogFilter
); 
 350         thePt
.h 
= thePt
.v 
= -1; // center dialog 
 352         ParamText( message 
, NULL 
, NULL 
, NULL 
) ; 
 354         CustomGetFile(  fileFilter
,  
 355                                         -1,                                     // show all types 
 359                                         thePt
,                          // top left point 
 362                                         nil
,                            // activate list 
 363                                         nil
,                            // activate proc 
 366         DisposeRoutineDescriptor(dlgHookUPP
); 
 367         DisposeRoutineDescriptor(myModalFilterUPP
); 
 371         // if cancel wasn't pressed and no fatal error occurred... 
 375                 // if no name is in the reply record file spec, 
 376                 // use the file spec of the parent folder 
 378                 if (theSFR
->sfFile
.name
[0] == '\0') 
 380                         err 
= FSMakeFSSpec(theSFR
->sfFile
.vRefNum
, theSFR
->sfFile
.parID
, 
 384                                 theSFR
->sfFile 
= tempSpec
; 
 388                                 // no name to return, forget it 
 390                                 theSFR
->sfGood 
= false; 
 394                 // if there is now a name in the file spec, check if it's 
 395                 // for a folder or a volume 
 397                 if (theSFR
->sfFile
.name
[0] != '\0') 
 399                         // the parID of the root of a disk is always fsRtParID == 1 
 401                         if (theSFR
->sfFile
.parID 
== fsRtParID
) 
 403                                 theSFR
->sfIsVolume 
= true; 
 404                                 theSFR
->sfIsFolder 
= false;     // it would be reasonable for this to be true, too 
 407                         // we have a valid FSSpec, now let's make sure it's not for an alias file 
 409                         err 
= ResolveAliasFile(&theSFR
->sfFile
, true, &folderFlag
, &wasAliasedFlag
); 
 412                                 theSFR
->sfGood 
= false; 
 415                         // did the alias resolve to a folder? 
 417                         if (folderFlag  
&& ! theSFR
->sfIsVolume
) 
 419                                 theSFR
->sfIsFolder 
= true; 
 425 static pascal Boolean 
OnlyVisibleFoldersCustomFileFilter(CInfoPBPtr myCInfoPBPtr
, Ptr dataPtr
) 
 427 #pragma unused (dataPtr) 
 429         // return true if this item is invisible or a file 
 434         visibleFlag 
= ! (myCInfoPBPtr
->hFileInfo
.ioFlFndrInfo
.fdFlags 
& kIsInvisible
); 
 435         folderFlag 
= (myCInfoPBPtr
->hFileInfo
.ioFlAttrib 
& 0x10); 
 437         // because the semantics of the filter proc are "true means don't show 
 438         // it" we need to invert the result that we return 
 440         return !(visibleFlag 
&& folderFlag
); 
 446 wxDirDialog::wxDirDialog(wxWindow 
*parent
, const wxString
& message
, 
 447         const wxString
& defaultPath
, 
 448         long style
, const wxPoint
& pos
) 
 451     m_dialogStyle 
= style
; 
 453         m_path 
= defaultPath
; 
 456 int wxDirDialog::ShowModal() 
 459         if ( !gUseNavServices 
) 
 465                 c2pstrcpy((StringPtr
)prompt
, m_message
) ; 
 467                 strcpy((char *)prompt
, m_message
) ; 
 468                 c2pstr((char *)prompt 
) ; 
 471                 c2pstrcpy((StringPtr
)path
, m_path 
) ; 
 473                 strcpy((char *)path
, m_path 
) ; 
 474                 c2pstr((char *)path 
) ; 
 477                 StandardFileReply       reply 
; 
 478                 FileFilterYDUPP         invisiblesExcludedCustomFilterUPP 
= 0 ; 
 479                 invisiblesExcludedCustomFilterUPP 
=  
 480                         NewFileFilterYDProc(OnlyVisibleFoldersCustomFileFilter
); 
 483                 StandardGetFolder( prompt 
, path 
, invisiblesExcludedCustomFilterUPP
, &reply
); 
 486                 DisposeRoutineDescriptor(invisiblesExcludedCustomFilterUPP
); 
 488                 if ( reply
.sfGood 
== false ) 
 495                         m_path 
= wxMacFSSpec2UnixFilename( &reply
.sfFile 
) ; 
 503                 NavDialogOptions                mNavOptions
; 
 504                 NavObjectFilterUPP              mNavFilterUPP 
= NULL
; 
 505                 NavPreviewUPP                   mNavPreviewUPP 
= NULL 
; 
 506                 NavReplyRecord                  mNavReply
; 
 507                 AEDesc
*                                 mDefaultLocation 
= NULL 
; 
 508                 bool                                    mSelectDefault 
= false ; 
 510                 ::NavGetDefaultDialogOptions(&mNavOptions
); 
 513                 mNavPreviewUPP  
= nil
; 
 514                 mSelectDefault  
= false; 
 515                 mNavReply
.validRecord                           
= false; 
 516                 mNavReply
.replacing                                     
= false; 
 517                 mNavReply
.isStationery                          
= false; 
 518                 mNavReply
.translationNeeded                     
= false; 
 519                 mNavReply
.selection
.descriptorType 
= typeNull
; 
 520                 mNavReply
.selection
.dataHandle          
= nil
; 
 521                 mNavReply
.keyScript                                     
= smSystemScript
; 
 522                 mNavReply
.fileTranslation                       
= nil
; 
 524                 // Set default location, the location 
 525                 //   that's displayed when the dialog 
 528                 if ( mDefaultLocation 
) { 
 530                         if (mSelectDefault
) { 
 531                                 mNavOptions
.dialogOptionFlags 
|= kNavSelectDefaultLocation
; 
 533                                 mNavOptions
.dialogOptionFlags 
&= ~kNavSelectDefaultLocation
; 
 537                 OSErr err 
= ::NavChooseFolder( 
 545                 if ( (err 
!= noErr
) && (err 
!= userCanceledErr
) ) { 
 550                 if (mNavReply
.validRecord
) {            // User chose a folder 
 556                         OSErr err 
= ::AECoerceDesc( &mNavReply
.selection 
, typeFSS
, &specDesc
); 
 557                         if ( err 
!= noErr 
) { 
 561                         folderInfo 
= **(FSSpec
**) specDesc
.dataHandle
; 
 562                         if (specDesc
.dataHandle 
!= nil
) { 
 563                                 ::AEDisposeDesc(&specDesc
); 
 566 //                      mNavReply.GetFileSpec(folderInfo); 
 568                                 // The FSSpec from NavChooseFolder is NOT the file spec 
 569                                 // for the folder. The parID field is actually the DirID 
 570                                 // of the folder itself, not the folder's parent, and 
 571                                 // the name field is empty. We must call PBGetCatInfo 
 572                                 // to get the parent DirID and folder name 
 575                         CInfoPBRec      thePB
;                  // Directory Info Parameter Block 
 576                         thePB
.dirInfo
.ioCompletion      
= nil
; 
 577                         thePB
.dirInfo
.ioVRefNum         
= folderInfo
.vRefNum
;   // Volume is right 
 578                         thePB
.dirInfo
.ioDrDirID         
= folderInfo
.parID
;             // Folder's DirID 
 579                         thePB
.dirInfo
.ioNamePtr         
= name
; 
 580                         thePB
.dirInfo
.ioFDirIndex       
= -1;   // Lookup using Volume and DirID 
 582                         err 
= ::PBGetCatInfoSync(&thePB
); 
 583                         if ( err 
!= noErr 
) { 
 587                                                                                                 // Create cannonical FSSpec 
 588                         ::FSMakeFSSpec(thePB
.dirInfo
.ioVRefNum
, thePB
.dirInfo
.ioDrParID
, 
 591                         // outFolderDirID = thePB.dirInfo.ioDrDirID; 
 592                         m_path 
= wxMacFSSpec2UnixFilename( &outFileSpec 
) ;