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"
23 #include "Navigation.h"
25 #if !USE_SHARED_LIBRARY
26 IMPLEMENT_CLASS(wxDirDialog
, wxDialog
)
29 bool gUseNavServices
= NavServicesAvailable() ;
31 // the data we need to pass to our standard file hook routine
32 // includes a pointer to the dialog, a pointer to the standard
33 // file reply record (so we can inspect the current selection)
34 // and a copy of the "previous" file spec of the reply record
35 // so we can see if the selection has changed
40 StandardFileReply
*sfrPtr
;
41 FSSpec oldSelectionFSSpec
;
44 typedef struct UserDataRec
45 UserDataRec
, *UserDataRecPtr
;
48 kSelectItem
= 10, // select button item number
49 kSFGetFolderDlgID
= 250, // dialog resource number
50 kStrListID
= 250, // our strings
51 kSelectStrNum
= 1, // word 'Select: ' for button
52 kDesktopStrNum
= 2, // word 'Desktop' for button
53 kSelectNoQuoteStrNum
= 3, // word 'Select: ' for button
55 kUseQuotes
= true, // parameter for SetButtonName
56 kDontUseQuotes
= false
60 static void GetLabelString(StringPtr theStr
, short stringNum
)
62 GetIndString(theStr
, kStrListID
, stringNum
);
65 static void CopyPStr(StringPtr src
, StringPtr dest
)
67 BlockMoveData(src
, dest
, 1 + src
[0]);
70 static char GetSelectKey(void)
72 // this is the key used to trigger the select button
74 // NOT INTERNATIONAL SAVVY; should at least grab it from resources
80 // SetButtonName sets the name of the Select button in the dialog
82 // To do this, we need to call the Script Manager to truncate the
83 // label in the middle to fit the button and to merge the button
84 // name with the word Select (possibly followed by quotes). Using
85 // the Script Manager avoids all sorts of problems internationally.
87 // buttonName is the name to appear following the word Select
88 // quoteFlag should be true if the name is to appear in quotes
90 static void SetButtonName(DialogPtr theDlgPtr
, short buttonID
, StringPtr buttonName
,
106 // get the details of the button from the dialog
108 GetDialogItem(theDlgPtr
, buttonID
, &buttonType
, &buttonHandle
, &buttonRect
);
110 // get the string for the select button label, "Select ^0" or "Select Ò^0Ó"
112 GetLabelString(labelStr
, (quoteFlag
== kUseQuotes
) ? kSelectStrNum
: kSelectNoQuoteStrNum
);
114 // make string handles containing the select button label and the
115 // file name to be stuffed into the button
117 err
= PtrToHand(&labelStr
[1], &labelHandle
, labelStr
[0]);
118 if (err
!= noErr
) goto Bail
;
120 // cut out the middle of the file name to fit the button
122 // we'll temporarily use labelStr here to hold the modified button name
123 // since we don't own the buttonName string storage space
125 textWidth
= (buttonRect
.right
- buttonRect
.left
) - StringWidth(labelStr
);
127 CopyPStr(buttonName
, labelStr
);
128 (void) TruncString(textWidth
, labelStr
, smTruncMiddle
);
130 err
= PtrToHand(&labelStr
[1], &nameHandle
, labelStr
[0]);
131 if (err
!= noErr
) goto Bail
;
133 // replace the ^0 in the Select string with the file name
135 CopyPStr("\p^0", keyStr
);
137 (void) ReplaceText(labelHandle
, nameHandle
, keyStr
);
139 labelStr
[0] = (unsigned char) GetHandleSize(labelHandle
);
140 BlockMoveData(*labelHandle
, &labelStr
[1], labelStr
[0]);
142 // now set the control title, and re-validate the area
143 // above the control to avoid a needless redraw
145 SetControlTitle((ControlHandle
) buttonHandle
, labelStr
);
147 ValidRect(&buttonRect
);
150 if (nameHandle
) DisposeHandle(nameHandle
);
151 if (labelHandle
) DisposeHandle(labelHandle
);
155 // FlashButton briefly highlights the dialog button
156 // as feedback for key equivalents
158 static void FlashButton(DialogPtr theDlgPtr
, short buttonID
)
163 unsigned long finalTicks
;
165 GetDialogItem(theDlgPtr
, buttonID
, &buttonType
, &buttonHandle
, &buttonRect
);
166 HiliteControl((ControlHandle
) buttonHandle
, kControlButtonPart
);
167 Delay(10, &finalTicks
);
168 HiliteControl((ControlHandle
) buttonHandle
, 0);
171 static Boolean
SameFSSpec(FSSpecPtr spec1
, FSSpecPtr spec2
)
173 return (spec1
->vRefNum
== spec2
->vRefNum
174 && spec1
->parID
== spec2
->parID
175 && EqualString(spec1
->name
, spec2
->name
, false, false));
177 // MyModalDialogFilter maps a key to the Select button, and handles
178 // flashing of the button when the key is hit
180 static pascal Boolean
SFGetFolderModalDialogFilter(DialogPtr theDlgPtr
, EventRecord
*eventRec
,
181 short *item
, Ptr dataPtr
)
183 #pragma unused (dataPtr)
185 // make certain the proper dialog is showing, 'cause standard file
186 // can nest dialogs but calls the same filter for each
188 if (((WindowPeek
) theDlgPtr
)->refCon
== sfMainDialogRefCon
)
190 // check if the select button was hit
192 if ((eventRec
->what
== keyDown
)
193 && (eventRec
->modifiers
& cmdKey
)
194 && ((eventRec
->message
& charCodeMask
) == GetSelectKey()))
197 FlashButton(theDlgPtr
, kSelectItem
);
206 // MyDlgHook is a hook routine that maps the select button to Open
207 // and sets the Select button name
209 static pascal short SFGetFolderDialogHook(short item
, DialogPtr theDlgPtr
, Ptr dataPtr
)
211 UserDataRecPtr theUserDataRecPtr
;
213 short desktopVRefNum
;
218 // be sure Std File is really showing us the intended dialog,
219 // not a nested modal dialog
221 if (((WindowPeek
) theDlgPtr
)->refCon
!= sfMainDialogRefCon
)
226 theUserDataRecPtr
= (UserDataRecPtr
) dataPtr
;
228 // map the Select button to Open
230 if (item
== kSelectItem
)
232 item
= sfItemOpenButton
;
235 // find the desktop folder
237 err
= FindFolder(theUserDataRecPtr
->sfrPtr
->sfFile
.vRefNum
,
238 kDesktopFolderType
, kDontCreateFolder
,
239 &desktopVRefNum
, &desktopDirID
);
243 // for errors, get value that won't match any real vRefNum/dirID
248 // change the Select button label if the selection has changed or
249 // if this is the first call to the hook
251 if (item
== sfHookFirstCall
252 || item
== sfHookChangeSelection
253 || item
== sfHookRebuildList
254 || ! SameFSSpec(&theUserDataRecPtr
->sfrPtr
->sfFile
,
255 &theUserDataRecPtr
->oldSelectionFSSpec
))
257 // be sure there is a file name selected
259 if (theUserDataRecPtr
->sfrPtr
->sfFile
.name
[0] != '\0')
261 SetButtonName(theDlgPtr
, kSelectItem
,
262 theUserDataRecPtr
->sfrPtr
->sfFile
.name
,
263 kUseQuotes
); // true -> use quotes
267 // is the desktop selected?
269 if (theUserDataRecPtr
->sfrPtr
->sfFile
.vRefNum
== desktopVRefNum
270 && theUserDataRecPtr
->sfrPtr
->sfFile
.parID
== desktopDirID
)
272 // set button to "Select Desktop"
274 GetLabelString(desktopName
, kDesktopStrNum
);
275 SetButtonName(theDlgPtr
, kSelectItem
,
276 desktopName
, kDontUseQuotes
); // false -> no quotes
280 // get parent directory's name for the Select button
282 // passing an empty name string to FSMakeFSSpec gets the
283 // name of the folder specified by the parID parameter
285 (void) FSMakeFSSpec(theUserDataRecPtr
->sfrPtr
->sfFile
.vRefNum
,
286 theUserDataRecPtr
->sfrPtr
->sfFile
.parID
, "\p",
288 SetButtonName(theDlgPtr
, kSelectItem
,
289 tempSpec
.name
, kUseQuotes
); // true -> use quotes
294 // save the current selection as the old selection for comparison next time
296 // it's not valid on the first call, though, or if we don't have a
297 // name available from standard file
299 if (item
!= sfHookFirstCall
|| theUserDataRecPtr
->sfrPtr
->sfFile
.name
[0] != '\0')
301 theUserDataRecPtr
->oldSelectionFSSpec
= theUserDataRecPtr
->sfrPtr
->sfFile
;
305 // on first call, empty string won't set the button correctly,
306 // so invalidate oldSelection
308 theUserDataRecPtr
->oldSelectionFSSpec
.vRefNum
= 999;
309 theUserDataRecPtr
->oldSelectionFSSpec
.parID
= 0;
315 void StandardGetFolder( ConstStr255Param message
, ConstStr255Param path
, FileFilterYDUPP fileFilter
, StandardFileReply
*theSFR
)
318 SFTypeList mySFTypeList
;
322 Boolean wasAliasedFlag
;
323 DlgHookYDUPP dlgHookUPP
;
324 ModalFilterYDUPP myModalFilterUPP
;
328 // presumably we're running System 7 or later so CustomGetFile is
331 // set initial contents of Select button to a space
333 memcpy(theSFR
->sfFile
.name
, "\p ", 2);
335 // point the user data parameter at the reply record so we can get to it later
337 myData
.sfrPtr
= theSFR
;
339 // display the dialog
343 dlgHookUPP
= NewDlgHookYDProc(SFGetFolderDialogHook
);
344 myModalFilterUPP
= NewModalFilterYDProc(SFGetFolderModalDialogFilter
);
346 thePt
.h
= thePt
.v
= -1; // center dialog
348 ParamText( message
, NULL
, NULL
, NULL
) ;
350 CustomGetFile( fileFilter
,
351 -1, // show all types
355 thePt
, // top left point
358 nil
, // activate list
359 nil
, // activate proc
362 DisposeRoutineDescriptor(dlgHookUPP
);
363 DisposeRoutineDescriptor(myModalFilterUPP
);
367 // if cancel wasn't pressed and no fatal error occurred...
371 // if no name is in the reply record file spec,
372 // use the file spec of the parent folder
374 if (theSFR
->sfFile
.name
[0] == '\0')
376 err
= FSMakeFSSpec(theSFR
->sfFile
.vRefNum
, theSFR
->sfFile
.parID
,
380 theSFR
->sfFile
= tempSpec
;
384 // no name to return, forget it
386 theSFR
->sfGood
= false;
390 // if there is now a name in the file spec, check if it's
391 // for a folder or a volume
393 if (theSFR
->sfFile
.name
[0] != '\0')
395 // the parID of the root of a disk is always fsRtParID == 1
397 if (theSFR
->sfFile
.parID
== fsRtParID
)
399 theSFR
->sfIsVolume
= true;
400 theSFR
->sfIsFolder
= false; // it would be reasonable for this to be true, too
403 // we have a valid FSSpec, now let's make sure it's not for an alias file
405 err
= ResolveAliasFile(&theSFR
->sfFile
, true, &folderFlag
, &wasAliasedFlag
);
408 theSFR
->sfGood
= false;
411 // did the alias resolve to a folder?
413 if (folderFlag
&& ! theSFR
->sfIsVolume
)
415 theSFR
->sfIsFolder
= true;
421 static pascal Boolean
OnlyVisibleFoldersCustomFileFilter(CInfoPBPtr myCInfoPBPtr
, Ptr dataPtr
)
423 #pragma unused (dataPtr)
425 // return true if this item is invisible or a file
430 visibleFlag
= ! (myCInfoPBPtr
->hFileInfo
.ioFlFndrInfo
.fdFlags
& kIsInvisible
);
431 folderFlag
= (myCInfoPBPtr
->hFileInfo
.ioFlAttrib
& 0x10);
433 // because the semantics of the filter proc are "true means don't show
434 // it" we need to invert the result that we return
436 return !(visibleFlag
&& folderFlag
);
442 wxDirDialog::wxDirDialog(wxWindow
*parent
, const wxString
& message
,
443 const wxString
& defaultPath
,
444 long style
, const wxPoint
& pos
)
447 m_dialogStyle
= style
;
449 m_path
= defaultPath
;
452 int wxDirDialog::ShowModal()
455 if ( !gUseNavServices
)
460 strcpy((char *)prompt
, m_message
) ;
461 c2pstr((char *)prompt
) ;
463 strcpy((char *)path
, m_path
) ;
464 c2pstr((char *)path
) ;
466 StandardFileReply reply
;
467 FileFilterYDUPP invisiblesExcludedCustomFilterUPP
= 0 ;
468 invisiblesExcludedCustomFilterUPP
=
469 NewFileFilterYDProc(OnlyVisibleFoldersCustomFileFilter
);
472 StandardGetFolder( prompt
, path
, invisiblesExcludedCustomFilterUPP
, &reply
);
475 DisposeRoutineDescriptor(invisiblesExcludedCustomFilterUPP
);
477 if ( reply
.sfGood
== false )
484 m_path
= wxMacFSSpec2UnixFilename( &reply
.sfFile
) ;
492 NavDialogOptions mNavOptions
;
493 NavObjectFilterUPP mNavFilterUPP
= NULL
;
494 NavPreviewUPP mNavPreviewUPP
= NULL
;
495 NavReplyRecord mNavReply
;
496 AEDesc
* mDefaultLocation
= NULL
;
497 bool mSelectDefault
= false ;
499 ::NavGetDefaultDialogOptions(&mNavOptions
);
502 mNavPreviewUPP
= nil
;
503 mSelectDefault
= false;
504 mNavReply
.validRecord
= false;
505 mNavReply
.replacing
= false;
506 mNavReply
.isStationery
= false;
507 mNavReply
.translationNeeded
= false;
508 mNavReply
.selection
.descriptorType
= typeNull
;
509 mNavReply
.selection
.dataHandle
= nil
;
510 mNavReply
.keyScript
= smSystemScript
;
511 mNavReply
.fileTranslation
= nil
;
513 // Set default location, the location
514 // that's displayed when the dialog
517 if ( mDefaultLocation
) {
519 if (mSelectDefault
) {
520 mNavOptions
.dialogOptionFlags
|= kNavSelectDefaultLocation
;
522 mNavOptions
.dialogOptionFlags
&= ~kNavSelectDefaultLocation
;
526 OSErr err
= ::NavChooseFolder(
534 if ( (err
!= noErr
) && (err
!= userCanceledErr
) ) {
539 if (mNavReply
.validRecord
) { // User chose a folder
545 OSErr err
= ::AECoerceDesc( &mNavReply
.selection
, typeFSS
, &specDesc
);
546 if ( err
!= noErr
) {
550 folderInfo
= **(FSSpec
**) specDesc
.dataHandle
;
551 if (specDesc
.dataHandle
!= nil
) {
552 ::AEDisposeDesc(&specDesc
);
555 // mNavReply.GetFileSpec(folderInfo);
557 // The FSSpec from NavChooseFolder is NOT the file spec
558 // for the folder. The parID field is actually the DirID
559 // of the folder itself, not the folder's parent, and
560 // the name field is empty. We must call PBGetCatInfo
561 // to get the parent DirID and folder name
564 CInfoPBRec thePB
; // Directory Info Parameter Block
565 thePB
.dirInfo
.ioCompletion
= nil
;
566 thePB
.dirInfo
.ioVRefNum
= folderInfo
.vRefNum
; // Volume is right
567 thePB
.dirInfo
.ioDrDirID
= folderInfo
.parID
; // Folder's DirID
568 thePB
.dirInfo
.ioNamePtr
= name
;
569 thePB
.dirInfo
.ioFDirIndex
= -1; // Lookup using Volume and DirID
571 err
= ::PBGetCatInfoSync(&thePB
);
572 if ( err
!= noErr
) {
576 // Create cannonical FSSpec
577 ::FSMakeFSSpec(thePB
.dirInfo
.ioVRefNum
, thePB
.dirInfo
.ioDrParID
,
580 // outFolderDirID = thePB.dirInfo.ioDrDirID;
581 m_path
= wxMacFSSpec2UnixFilename( &outFileSpec
) ;