]> git.saurik.com Git - wxWidgets.git/blob - src/mac/dirdlg.cpp
file dialog now remebers list/report and hidden files settings, displays icons for...
[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 IMPLEMENT_CLASS(wxDirDialog, wxDialog)
24
25 enum {
26 kSelectItem = 10, // select button item number
27 kSFGetFolderDlgID = 250, // dialog resource number
28 kStrListID = 250, // our strings
29 kSelectStrNum = 1, // word 'Select: ' for button
30 kDesktopStrNum = 2, // word 'Desktop' for button
31 kSelectNoQuoteStrNum = 3, // word 'Select: ' for button
32
33 kUseQuotes = true, // parameter for SetButtonName
34 kDontUseQuotes = false
35 };
36
37 // the data we need to pass to our standard file hook routine
38 // includes a pointer to the dialog, a pointer to the standard
39 // file reply record (so we can inspect the current selection)
40 // and a copy of the "previous" file spec of the reply record
41 // so we can see if the selection has changed
42
43 struct UserDataRec {
44 StandardFileReply *sfrPtr;
45 FSSpec oldSelectionFSSpec;
46 DialogPtr theDlgPtr;
47 };
48 typedef struct UserDataRec
49 UserDataRec, *UserDataRecPtr;
50
51 static void GetLabelString(StringPtr theStr, short stringNum)
52 {
53 GetIndString(theStr, kStrListID, stringNum);
54 }
55
56 static void CopyPStr(StringPtr src, StringPtr dest)
57 {
58 BlockMoveData(src, dest, 1 + src[0]);
59 }
60
61 static char GetSelectKey(void)
62 {
63 // this is the key used to trigger the select button
64
65 // NOT INTERNATIONAL SAVVY; should at least grab it from resources
66
67 return 's';
68 }
69
70
71 // SetButtonName sets the name of the Select button in the dialog
72 //
73 // To do this, we need to call the Script Manager to truncate the
74 // label in the middle to fit the button and to merge the button
75 // name with the word Select (possibly followed by quotes). Using
76 // the Script Manager avoids all sorts of problems internationally.
77 //
78 // buttonName is the name to appear following the word Select
79 // quoteFlag should be true if the name is to appear in quotes
80
81 static void SetButtonName(DialogPtr theDlgPtr, short buttonID, StringPtr buttonName,
82 Boolean quoteFlag)
83 {
84 short buttonType;
85 Handle buttonHandle;
86 Rect buttonRect;
87 short textWidth;
88 Handle labelHandle;
89 Handle nameHandle;
90 Str15 keyStr;
91 Str255 labelStr;
92 OSErr err;
93
94 nameHandle = nil;
95 labelHandle = nil;
96
97 // get the details of the button from the dialog
98
99 GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
100
101 // get the string for the select button label, "Select ^0" or "Select Ò^0Ó"
102
103 GetLabelString(labelStr, (quoteFlag == kUseQuotes) ? kSelectStrNum : kSelectNoQuoteStrNum);
104
105 // make string handles containing the select button label and the
106 // file name to be stuffed into the button
107
108 err = PtrToHand(&labelStr[1], &labelHandle, labelStr[0]);
109 if (err != noErr) goto Bail;
110
111 // cut out the middle of the file name to fit the button
112 //
113 // we'll temporarily use labelStr here to hold the modified button name
114 // since we don't own the buttonName string storage space
115
116 textWidth = (buttonRect.right - buttonRect.left) - StringWidth(labelStr);
117
118 CopyPStr(buttonName, labelStr);
119 (void) TruncString(textWidth, labelStr, smTruncMiddle);
120
121 err = PtrToHand(&labelStr[1], &nameHandle, labelStr[0]);
122 if (err != noErr) goto Bail;
123
124 // replace the ^0 in the Select string with the file name
125
126 CopyPStr("\p^0", keyStr);
127
128 (void) ReplaceText(labelHandle, nameHandle, keyStr);
129
130 labelStr[0] = (unsigned char) GetHandleSize(labelHandle);
131 BlockMoveData(*labelHandle, &labelStr[1], labelStr[0]);
132
133 // now set the control title, and re-validate the area
134 // above the control to avoid a needless redraw
135
136 SetControlTitle((ControlHandle) buttonHandle, labelStr);
137
138 ValidRect(&buttonRect);
139
140 Bail:
141 if (nameHandle) DisposeHandle(nameHandle);
142 if (labelHandle) DisposeHandle(labelHandle);
143
144 }
145
146 // FlashButton briefly highlights the dialog button
147 // as feedback for key equivalents
148
149 static void FlashButton(DialogPtr theDlgPtr, short buttonID)
150 {
151 short buttonType;
152 Handle buttonHandle;
153 Rect buttonRect;
154 unsigned long finalTicks;
155
156 GetDialogItem(theDlgPtr, buttonID, &buttonType, &buttonHandle, &buttonRect);
157 HiliteControl((ControlHandle) buttonHandle, kControlButtonPart);
158 Delay(10, &finalTicks);
159 HiliteControl((ControlHandle) buttonHandle, 0);
160 }
161
162 static Boolean SameFSSpec(FSSpecPtr spec1, FSSpecPtr spec2)
163 {
164 return (spec1->vRefNum == spec2->vRefNum
165 && spec1->parID == spec2->parID
166 && EqualString(spec1->name, spec2->name, false, false));
167 }
168 // MyModalDialogFilter maps a key to the Select button, and handles
169 // flashing of the button when the key is hit
170
171 static pascal Boolean SFGetFolderModalDialogFilter(DialogPtr theDlgPtr, EventRecord *eventRec,
172 short *item, Ptr dataPtr)
173 {
174 #pragma unused (dataPtr)
175
176 // make certain the proper dialog is showing, 'cause standard file
177 // can nest dialogs but calls the same filter for each
178
179 if (((WindowPeek) theDlgPtr)->refCon == sfMainDialogRefCon)
180 {
181 // check if the select button was hit
182
183 if ((eventRec->what == keyDown)
184 && (eventRec->modifiers & cmdKey)
185 && ((eventRec->message & charCodeMask) == GetSelectKey()))
186 {
187 *item = kSelectItem;
188 FlashButton(theDlgPtr, kSelectItem);
189 return true;
190 }
191 }
192
193 return false;
194 }
195
196
197 // MyDlgHook is a hook routine that maps the select button to Open
198 // and sets the Select button name
199
200 static pascal short SFGetFolderDialogHook(short item, DialogPtr theDlgPtr, Ptr dataPtr)
201 {
202 UserDataRecPtr theUserDataRecPtr;
203 long desktopDirID;
204 short desktopVRefNum;
205 FSSpec tempSpec;
206 Str63 desktopName;
207 OSErr err;
208
209 // be sure Std File is really showing us the intended dialog,
210 // not a nested modal dialog
211
212 if (((WindowPeek) theDlgPtr)->refCon != sfMainDialogRefCon)
213 {
214 return item;
215 }
216
217 theUserDataRecPtr = (UserDataRecPtr) dataPtr;
218
219 // map the Select button to Open
220
221 if (item == kSelectItem)
222 {
223 item = sfItemOpenButton;
224 }
225
226 // find the desktop folder
227
228 err = FindFolder(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
229 kDesktopFolderType, kDontCreateFolder,
230 &desktopVRefNum, &desktopDirID);
231
232 if (err != noErr)
233 {
234 // for errors, get value that won't match any real vRefNum/dirID
235 desktopVRefNum = 0;
236 desktopDirID = 0;
237 }
238
239 // change the Select button label if the selection has changed or
240 // if this is the first call to the hook
241
242 if (item == sfHookFirstCall
243 || item == sfHookChangeSelection
244 || item == sfHookRebuildList
245 || ! SameFSSpec(&theUserDataRecPtr->sfrPtr->sfFile,
246 &theUserDataRecPtr->oldSelectionFSSpec))
247 {
248 // be sure there is a file name selected
249
250 if (theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
251 {
252 SetButtonName(theDlgPtr, kSelectItem,
253 theUserDataRecPtr->sfrPtr->sfFile.name,
254 kUseQuotes); // true -> use quotes
255 }
256 else
257 {
258 // is the desktop selected?
259
260 if (theUserDataRecPtr->sfrPtr->sfFile.vRefNum == desktopVRefNum
261 && theUserDataRecPtr->sfrPtr->sfFile.parID == desktopDirID)
262 {
263 // set button to "Select Desktop"
264
265 GetLabelString(desktopName, kDesktopStrNum);
266 SetButtonName(theDlgPtr, kSelectItem,
267 desktopName, kDontUseQuotes); // false -> no quotes
268 }
269 else
270 {
271 // get parent directory's name for the Select button
272 //
273 // passing an empty name string to FSMakeFSSpec gets the
274 // name of the folder specified by the parID parameter
275
276 (void) FSMakeFSSpec(theUserDataRecPtr->sfrPtr->sfFile.vRefNum,
277 theUserDataRecPtr->sfrPtr->sfFile.parID, "\p",
278 &tempSpec);
279 SetButtonName(theDlgPtr, kSelectItem,
280 tempSpec.name, kUseQuotes); // true -> use quotes
281 }
282 }
283 }
284
285 // save the current selection as the old selection for comparison next time
286 //
287 // it's not valid on the first call, though, or if we don't have a
288 // name available from standard file
289
290 if (item != sfHookFirstCall || theUserDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
291 {
292 theUserDataRecPtr->oldSelectionFSSpec = theUserDataRecPtr->sfrPtr->sfFile;
293 }
294 else
295 {
296 // on first call, empty string won't set the button correctly,
297 // so invalidate oldSelection
298
299 theUserDataRecPtr->oldSelectionFSSpec.vRefNum = 999;
300 theUserDataRecPtr->oldSelectionFSSpec.parID = 0;
301 }
302
303 return item;
304 }
305
306 void StandardGetFolder( ConstStr255Param message , ConstStr255Param path , FileFilterYDUPP fileFilter, StandardFileReply *theSFR)
307 {
308 Point thePt;
309 SFTypeList mySFTypeList;
310 UserDataRec myData;
311 FSSpec tempSpec;
312 Boolean folderFlag;
313 Boolean wasAliasedFlag;
314 DlgHookYDUPP dlgHookUPP;
315 ModalFilterYDUPP myModalFilterUPP;
316 OSErr err;
317
318
319 // presumably we're running System 7 or later so CustomGetFile is
320 // available
321
322 // set initial contents of Select button to a space
323
324 CopyPStr("\p ", theSFR->sfFile.name);
325
326 // point the user data parameter at the reply record so we can get to it later
327
328 myData.sfrPtr = theSFR;
329
330 // display the dialog
331
332 dlgHookUPP = NewDlgHookYDProc(SFGetFolderDialogHook);
333 myModalFilterUPP = NewModalFilterYDProc(SFGetFolderModalDialogFilter);
334
335 thePt.h = thePt.v = -1; // center dialog
336
337 ParamText( message , NULL , NULL , NULL ) ;
338
339 CustomGetFile( fileFilter,
340 -1, // show all types
341 mySFTypeList,
342 theSFR,
343 kSFGetFolderDlgID,
344 thePt, // top left point
345 dlgHookUPP,
346 myModalFilterUPP,
347 nil, // activate list
348 nil, // activate proc
349 &myData);
350
351 DisposeRoutineDescriptor(dlgHookUPP);
352 DisposeRoutineDescriptor(myModalFilterUPP);
353
354 // if cancel wasn't pressed and no fatal error occurred...
355
356 if (theSFR->sfGood)
357 {
358 // if no name is in the reply record file spec,
359 // use the file spec of the parent folder
360
361 if (theSFR->sfFile.name[0] == '\0')
362 {
363 err = FSMakeFSSpec(theSFR->sfFile.vRefNum, theSFR->sfFile.parID,
364 "\p", &tempSpec);
365 if (err == noErr)
366 {
367 theSFR->sfFile = tempSpec;
368 }
369 else
370 {
371 // no name to return, forget it
372
373 theSFR->sfGood = false;
374 }
375 }
376
377 // if there is now a name in the file spec, check if it's
378 // for a folder or a volume
379
380 if (theSFR->sfFile.name[0] != '\0')
381 {
382 // the parID of the root of a disk is always fsRtParID == 1
383
384 if (theSFR->sfFile.parID == fsRtParID)
385 {
386 theSFR->sfIsVolume = true;
387 theSFR->sfIsFolder = false; // it would be reasonable for this to be true, too
388 }
389
390 // we have a valid FSSpec, now let's make sure it's not for an alias file
391
392 err = ResolveAliasFile(&theSFR->sfFile, true, &folderFlag, &wasAliasedFlag);
393 if (err != noErr)
394 {
395 theSFR->sfGood = false;
396 }
397
398 // did the alias resolve to a folder?
399
400 if (folderFlag && ! theSFR->sfIsVolume)
401 {
402 theSFR->sfIsFolder = true;
403 }
404 }
405 }
406 }
407
408 static pascal Boolean OnlyVisibleFoldersCustomFileFilter(CInfoPBPtr myCInfoPBPtr, Ptr dataPtr)
409 {
410 #pragma unused (dataPtr)
411
412 // return true if this item is invisible or a file
413
414 Boolean visibleFlag;
415 Boolean folderFlag;
416
417 visibleFlag = ! (myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible);
418 folderFlag = (myCInfoPBPtr->hFileInfo.ioFlAttrib & 0x10);
419
420 // because the semantics of the filter proc are "true means don't show
421 // it" we need to invert the result that we return
422
423 return !(visibleFlag && folderFlag);
424 }
425
426 wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message,
427 const wxString& defaultPath,
428 long style, const wxPoint& pos)
429 {
430 m_message = message;
431 m_dialogStyle = style;
432 m_parent = parent;
433 m_path = defaultPath;
434 }
435
436 int wxDirDialog::ShowModal()
437 {
438 {
439 Str255 prompt ;
440 Str255 path ;
441
442 strcpy((char *)prompt, m_message) ;
443 c2pstr((char *)prompt ) ;
444
445 strcpy((char *)path, m_path ) ;
446 c2pstr((char *)path ) ;
447
448 FileFilterYDUPP invisiblesExcludedCustomFilterUPP;
449 StandardFileReply reply ;
450 invisiblesExcludedCustomFilterUPP =
451 NewFileFilterYDProc(OnlyVisibleFoldersCustomFileFilter);
452
453 StandardGetFolder( prompt , path , invisiblesExcludedCustomFilterUPP, &reply);
454
455 DisposeRoutineDescriptor(invisiblesExcludedCustomFilterUPP);
456 if ( reply.sfGood == false )
457 {
458 m_path = "" ;
459 return wxID_CANCEL ;
460 }
461 else
462 {
463 m_path = wxMacFSSpec2UnixFilename( &reply.sfFile ) ;
464 return wxID_OK ;
465 }
466 }
467 return wxID_CANCEL;
468 }
469