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