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 <Carbon/Carbon.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
, void *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
, void *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
, void *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
= wxMacFSSpec2MacFilename( &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
= wxMacFSSpec2MacFilename( &outFileSpec
) ;