]> git.saurik.com Git - wxWidgets.git/blob - src/mac/dirdlg.cpp
Copied/merged from the 2.2 branch.
[wxWidgets.git] / src / mac / dirdlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: dirdlg.cpp
3 // Purpose: wxDirDialog
4 // Author: AUTHOR
5 // Modified by:
6 // Created: ??/??/98
7 // RCS-ID: $Id$
8 // Copyright: (c) AUTHOR
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #ifdef __GNUG__
13 #pragma implementation "dirdlg.h"
14 #endif
15
16 #include "wx/defs.h"
17 #include "wx/utils.h"
18 #include "wx/dialog.h"
19 #include "wx/dirdlg.h"
20
21 #include "wx/cmndata.h"
22
23 #if defined(__UNIX__)
24 #include <NavigationServices/Navigation.h>
25 #else
26 #include <Navigation.h>
27 #endif
28
29 #if !USE_SHARED_LIBRARY
30 IMPLEMENT_CLASS(wxDirDialog, wxDialog)
31 #endif
32
33 bool gUseNavServices = NavServicesAvailable() ;
34
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
40
41 #if !TARGET_CARBON
42
43 struct UserDataRec {
44 StandardFileReply *sfrPtr;
45 FSSpec oldSelectionFSSpec;
46 DialogPtr theDlgPtr;
47 };
48 typedef struct UserDataRec
49 UserDataRec, *UserDataRecPtr;
50
51 enum {
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
58
59 kUseQuotes = true, // parameter for SetButtonName
60 kDontUseQuotes = false
61 };
62
63
64 static void GetLabelString(StringPtr theStr, short stringNum)
65 {
66 GetIndString(theStr, kStrListID, stringNum);
67 }
68
69 static void CopyPStr(StringPtr src, StringPtr dest)
70 {
71 BlockMoveData(src, dest, 1 + src[0]);
72 }
73
74 static char GetSelectKey(void)
75 {
76 // this is the key used to trigger the select button
77
78 // NOT INTERNATIONAL SAVVY; should at least grab it from resources
79
80 return 's';
81 }
82
83
84 // SetButtonName sets the name of the Select button in the dialog
85 //
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.
90 //
91 // buttonName is the name to appear following the word Select
92 // quoteFlag should be true if the name is to appear in quotes
93
94 static void SetButtonName(DialogPtr theDlgPtr, short buttonID, StringPtr buttonName,
95 Boolean quoteFlag)
96 {
97 short buttonType;
98 Handle buttonHandle;
99 Rect buttonRect;
100 short textWidth;
101 Handle labelHandle;
102 Handle nameHandle;
103 Str15 keyStr;
104 Str255 labelStr;
105 OSErr err;
106
107 nameHandle = nil;
108 labelHandle = nil;
109
110 // get the details of the button from the dialog
111
112 GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
113
114 // get the string for the select button label, "Select ^0" or "Select Ò^0Ó"
115
116 GetLabelString(labelStr, (quoteFlag == kUseQuotes) ? kSelectStrNum : kSelectNoQuoteStrNum);
117
118 // make string handles containing the select button label and the
119 // file name to be stuffed into the button
120
121 err = PtrToHand(&labelStr[1], &labelHandle, labelStr[0]);
122 if (err != noErr) goto Bail;
123
124 // cut out the middle of the file name to fit the button
125 //
126 // we'll temporarily use labelStr here to hold the modified button name
127 // since we don't own the buttonName string storage space
128
129 textWidth = (buttonRect.right - buttonRect.left) - StringWidth(labelStr);
130
131 CopyPStr(buttonName, labelStr);
132 (void) TruncString(textWidth, labelStr, smTruncMiddle);
133
134 err = PtrToHand(&labelStr[1], &nameHandle, labelStr[0]);
135 if (err != noErr) goto Bail;
136
137 // replace the ^0 in the Select string with the file name
138
139 CopyPStr("\p^0", keyStr);
140
141 (void) ReplaceText(labelHandle, nameHandle, keyStr);
142
143 labelStr[0] = (unsigned char) GetHandleSize(labelHandle);
144 BlockMoveData(*labelHandle, &labelStr[1], labelStr[0]);
145
146 // now set the control title, and re-validate the area
147 // above the control to avoid a needless redraw
148
149 SetControlTitle((ControlHandle) buttonHandle, labelStr);
150
151 ValidRect(&buttonRect);
152
153 Bail:
154 if (nameHandle) DisposeHandle(nameHandle);
155 if (labelHandle) DisposeHandle(labelHandle);
156
157 }
158
159 // FlashButton briefly highlights the dialog button
160 // as feedback for key equivalents
161
162 static void FlashButton(DialogPtr theDlgPtr, short buttonID)
163 {
164 short buttonType;
165 Handle buttonHandle;
166 Rect buttonRect;
167 unsigned long finalTicks;
168
169 GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
170 HiliteControl((ControlHandle) buttonHandle, kControlButtonPart);
171 Delay(10, &finalTicks);
172 HiliteControl((ControlHandle) buttonHandle, 0);
173 }
174
175 static Boolean SameFSSpec(FSSpecPtr spec1, FSSpecPtr spec2)
176 {
177 return (spec1->vRefNum == spec2->vRefNum
178 && spec1->parID == spec2->parID
179 && EqualString(spec1->name, spec2->name, false, false));
180 }
181 // MyModalDialogFilter maps a key to the Select button, and handles
182 // flashing of the button when the key is hit
183
184 static pascal Boolean SFGetFolderModalDialogFilter(DialogPtr theDlgPtr, EventRecord *eventRec,
185 short *item, Ptr dataPtr)
186 {
187 #pragma unused (dataPtr)
188
189 // make certain the proper dialog is showing, 'cause standard file
190 // can nest dialogs but calls the same filter for each
191
192 if (((WindowPeek) theDlgPtr)->refCon == sfMainDialogRefCon)
193 {
194 // check if the select button was hit
195
196 if ((eventRec->what == keyDown)
197 && (eventRec->modifiers & cmdKey)
198 && ((eventRec->message & charCodeMask) == GetSelectKey()))
199 {
200 *item = kSelectItem;
201 FlashButton(theDlgPtr, kSelectItem);
202 return true;
203 }
204 }
205
206 return false;
207 }
208
209
210 // MyDlgHook is a hook routine that maps the select button to Open
211 // and sets the Select button name
212
213 static pascal short SFGetFolderDialogHook(short item, DialogPtr theDlgPtr, Ptr dataPtr)
214 {
215 UserDataRecPtr theUserDataRecPtr;
216 long desktopDirID;
217 short desktopVRefNum;
218 FSSpec tempSpec;
219 Str63 desktopName;
220 OSErr err;
221
222 // be sure Std File is really showing us the intended dialog,
223 // not a nested modal dialog
224
225 if (((WindowPeek) theDlgPtr)->refCon != sfMainDialogRefCon)
226 {
227 return item;
228 }
229
230 theUserDataRecPtr = (UserDataRecPtr) dataPtr;
231
232 // map the Select button to Open
233
234 if (item == kSelectItem)
235 {
236 item = sfItemOpenButton;
237 }
238
239 // find the desktop folder
240
241 err = FindFolder(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
242 kDesktopFolderType, kDontCreateFolder,
243 &desktopVRefNum, &desktopDirID);
244
245 if (err != noErr)
246 {
247 // for errors, get value that won't match any real vRefNum/dirID
248 desktopVRefNum = 0;
249 desktopDirID = 0;
250 }
251
252 // change the Select button label if the selection has changed or
253 // if this is the first call to the hook
254
255 if (item == sfHookFirstCall
256 || item == sfHookChangeSelection
257 || item == sfHookRebuildList
258 || ! SameFSSpec(&theUserDataRecPtr->sfrPtr->sfFile,
259 &theUserDataRecPtr->oldSelectionFSSpec))
260 {
261 // be sure there is a file name selected
262
263 if (theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
264 {
265 SetButtonName(theDlgPtr, kSelectItem,
266 theUserDataRecPtr->sfrPtr->sfFile.name,
267 kUseQuotes); // true -> use quotes
268 }
269 else
270 {
271 // is the desktop selected?
272
273 if (theUserDataRecPtr->sfrPtr->sfFile.vRefNum == desktopVRefNum
274 && theUserDataRecPtr->sfrPtr->sfFile.parID == desktopDirID)
275 {
276 // set button to "Select Desktop"
277
278 GetLabelString(desktopName, kDesktopStrNum);
279 SetButtonName(theDlgPtr, kSelectItem,
280 desktopName, kDontUseQuotes); // false -> no quotes
281 }
282 else
283 {
284 // get parent directory's name for the Select button
285 //
286 // passing an empty name string to FSMakeFSSpec gets the
287 // name of the folder specified by the parID parameter
288
289 (void) FSMakeFSSpec(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
290 theUserDataRecPtr->sfrPtr->sfFile.parID, "\p",
291 &tempSpec);
292 SetButtonName(theDlgPtr, kSelectItem,
293 tempSpec.name, kUseQuotes); // true -> use quotes
294 }
295 }
296 }
297
298 // save the current selection as the old selection for comparison next time
299 //
300 // it's not valid on the first call, though, or if we don't have a
301 // name available from standard file
302
303 if (item != sfHookFirstCall || theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
304 {
305 theUserDataRecPtr->oldSelectionFSSpec = theUserDataRecPtr->sfrPtr->sfFile;
306 }
307 else
308 {
309 // on first call, empty string won't set the button correctly,
310 // so invalidate oldSelection
311
312 theUserDataRecPtr->oldSelectionFSSpec.vRefNum = 999;
313 theUserDataRecPtr->oldSelectionFSSpec.parID = 0;
314 }
315
316 return item;
317 }
318
319 void StandardGetFolder( ConstStr255Param message , ConstStr255Param path , FileFilterYDUPP fileFilter, StandardFileReply *theSFR)
320 {
321 Point thePt;
322 SFTypeList mySFTypeList;
323 UserDataRec myData;
324 FSSpec tempSpec;
325 Boolean folderFlag;
326 Boolean wasAliasedFlag;
327 DlgHookYDUPP dlgHookUPP;
328 ModalFilterYDUPP myModalFilterUPP;
329 OSErr err;
330
331
332 // presumably we're running System 7 or later so CustomGetFile is
333 // available
334
335 // set initial contents of Select button to a space
336
337 memcpy(theSFR->sfFile.name, "\p ", 2);
338
339 // point the user data parameter at the reply record so we can get to it later
340
341 myData.sfrPtr = theSFR;
342
343 // display the dialog
344
345 #if !TARGET_CARBON
346
347 dlgHookUPP = NewDlgHookYDProc(SFGetFolderDialogHook);
348 myModalFilterUPP = NewModalFilterYDProc(SFGetFolderModalDialogFilter);
349
350 thePt.h = thePt.v = -1; // center dialog
351
352 ParamText( message , NULL , NULL , NULL ) ;
353
354 CustomGetFile( fileFilter,
355 -1, // show all types
356 mySFTypeList,
357 theSFR,
358 kSFGetFolderDlgID,
359 thePt, // top left point
360 dlgHookUPP,
361 myModalFilterUPP,
362 nil, // activate list
363 nil, // activate proc
364 &myData);
365
366 DisposeRoutineDescriptor(dlgHookUPP);
367 DisposeRoutineDescriptor(myModalFilterUPP);
368 #else
369 #endif
370
371 // if cancel wasn't pressed and no fatal error occurred...
372
373 if (theSFR->sfGood)
374 {
375 // if no name is in the reply record file spec,
376 // use the file spec of the parent folder
377
378 if (theSFR->sfFile.name[0] == '\0')
379 {
380 err = FSMakeFSSpec(theSFR->sfFile.vRefNum, theSFR->sfFile.parID,
381 "\p", &tempSpec);
382 if (err == noErr)
383 {
384 theSFR->sfFile = tempSpec;
385 }
386 else
387 {
388 // no name to return, forget it
389
390 theSFR->sfGood = false;
391 }
392 }
393
394 // if there is now a name in the file spec, check if it's
395 // for a folder or a volume
396
397 if (theSFR->sfFile.name[0] != '\0')
398 {
399 // the parID of the root of a disk is always fsRtParID == 1
400
401 if (theSFR->sfFile.parID == fsRtParID)
402 {
403 theSFR->sfIsVolume = true;
404 theSFR->sfIsFolder = false; // it would be reasonable for this to be true, too
405 }
406
407 // we have a valid FSSpec, now let's make sure it's not for an alias file
408
409 err = ResolveAliasFile(&theSFR->sfFile, true, &folderFlag, &wasAliasedFlag);
410 if (err != noErr)
411 {
412 theSFR->sfGood = false;
413 }
414
415 // did the alias resolve to a folder?
416
417 if (folderFlag && ! theSFR->sfIsVolume)
418 {
419 theSFR->sfIsFolder = true;
420 }
421 }
422 }
423 }
424
425 static pascal Boolean OnlyVisibleFoldersCustomFileFilter(CInfoPBPtr myCInfoPBPtr, Ptr dataPtr)
426 {
427 #pragma unused (dataPtr)
428
429 // return true if this item is invisible or a file
430
431 Boolean visibleFlag;
432 Boolean folderFlag;
433
434 visibleFlag = ! (myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible);
435 folderFlag = (myCInfoPBPtr->hFileInfo.ioFlAttrib & 0x10);
436
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
439
440 return !(visibleFlag && folderFlag);
441 }
442
443 #endif
444
445
446 wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message,
447 const wxString& defaultPath,
448 long style, const wxPoint& pos)
449 {
450 m_message = message;
451 m_dialogStyle = style;
452 m_parent = parent;
453 m_path = defaultPath;
454 }
455
456 int wxDirDialog::ShowModal()
457 {
458 #if !TARGET_CARBON
459 if ( !gUseNavServices )
460 {
461 Str255 prompt ;
462 Str255 path ;
463
464 #if TARGET_CARBON
465 c2pstrcpy((StringPtr)prompt, m_message) ;
466 #else
467 strcpy((char *)prompt, m_message) ;
468 c2pstr((char *)prompt ) ;
469 #endif
470 #if TARGET_CARBON
471 c2pstrcpy((StringPtr)path, m_path ) ;
472 #else
473 strcpy((char *)path, m_path ) ;
474 c2pstr((char *)path ) ;
475 #endif
476
477 StandardFileReply reply ;
478 FileFilterYDUPP invisiblesExcludedCustomFilterUPP = 0 ;
479 invisiblesExcludedCustomFilterUPP =
480 NewFileFilterYDProc(OnlyVisibleFoldersCustomFileFilter);
481
482
483 StandardGetFolder( prompt , path , invisiblesExcludedCustomFilterUPP, &reply);
484
485
486 DisposeRoutineDescriptor(invisiblesExcludedCustomFilterUPP);
487
488 if ( reply.sfGood == false )
489 {
490 m_path = "" ;
491 return wxID_CANCEL ;
492 }
493 else
494 {
495 m_path = wxMacFSSpec2UnixFilename( &reply.sfFile ) ;
496 return wxID_OK ;
497 }
498 return wxID_CANCEL;
499 }
500 else
501 #endif
502 {
503 NavDialogOptions mNavOptions;
504 NavObjectFilterUPP mNavFilterUPP = NULL;
505 NavPreviewUPP mNavPreviewUPP = NULL ;
506 NavReplyRecord mNavReply;
507 AEDesc* mDefaultLocation = NULL ;
508 bool mSelectDefault = false ;
509
510 ::NavGetDefaultDialogOptions(&mNavOptions);
511
512 mNavFilterUPP = nil;
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;
523
524 // Set default location, the location
525 // that's displayed when the dialog
526 // first appears
527
528 if ( mDefaultLocation ) {
529
530 if (mSelectDefault) {
531 mNavOptions.dialogOptionFlags |= kNavSelectDefaultLocation;
532 } else {
533 mNavOptions.dialogOptionFlags &= ~kNavSelectDefaultLocation;
534 }
535 }
536
537 OSErr err = ::NavChooseFolder(
538 mDefaultLocation,
539 &mNavReply,
540 &mNavOptions,
541 NULL,
542 mNavFilterUPP,
543 0L); // User Data
544
545 if ( (err != noErr) && (err != userCanceledErr) ) {
546 m_path = "" ;
547 return wxID_CANCEL ;
548 }
549
550 if (mNavReply.validRecord) { // User chose a folder
551
552 FSSpec folderInfo;
553 FSSpec outFileSpec ;
554 AEDesc specDesc ;
555
556 OSErr err = ::AECoerceDesc( &mNavReply.selection , typeFSS, &specDesc);
557 if ( err != noErr ) {
558 m_path = "" ;
559 return wxID_CANCEL ;
560 }
561 folderInfo = **(FSSpec**) specDesc.dataHandle;
562 if (specDesc.dataHandle != nil) {
563 ::AEDisposeDesc(&specDesc);
564 }
565
566 // mNavReply.GetFileSpec(folderInfo);
567
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
573
574 Str255 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
581
582 err = ::PBGetCatInfoSync(&thePB);
583 if ( err != noErr ) {
584 m_path = "" ;
585 return wxID_CANCEL ;
586 }
587 // Create cannonical FSSpec
588 ::FSMakeFSSpec(thePB.dirInfo.ioVRefNum, thePB.dirInfo.ioDrParID,
589 name, &outFileSpec);
590
591 // outFolderDirID = thePB.dirInfo.ioDrDirID;
592 m_path = wxMacFSSpec2UnixFilename( &outFileSpec ) ;
593 return wxID_OK ;
594 }
595 return wxID_CANCEL;
596
597 }
598 }
599