adding support for extraControl on osx_carbon
[wxWidgets.git] / src / osx / carbon / filedlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/carbon/filedlg.cpp
3 // Purpose: wxFileDialog
4 // Author: Stefan Csomor
5 // Modified by:
6 // Created: 1998-01-01
7 // RCS-ID: $Id$
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #if wxUSE_FILEDLG
15
16 #include "wx/filedlg.h"
17
18 #ifndef WX_PRECOMP
19 #include "wx/intl.h"
20 #include "wx/app.h"
21 #include "wx/utils.h"
22 #include "wx/dialog.h"
23 #endif
24
25 #include "wx/tokenzr.h"
26 #include "wx/filename.h"
27
28 #include "wx/osx/private.h"
29
30 #ifndef __DARWIN__
31 #include <Navigation.h>
32 #include "PLStringFuncs.h"
33 #endif
34
35 IMPLEMENT_CLASS(wxFileDialog, wxFileDialogBase)
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 class OpenUserDataRec
44 {
45 public:
46 OpenUserDataRec( wxFileDialog* dialog );
47
48 bool FilterCallback( AEDesc *theItem, void *info, NavFilterModes filterMode );
49 void EventProc( NavEventCallbackMessage inSelector, NavCBRecPtr ioParams );
50
51 int GetCurrentFilter() const {return currentfilter;}
52 CFArrayRef GetMenuItems() const { return menuitems;}
53
54
55 private:
56 void EventProcCBEvent( NavCBRecPtr ioParams );
57 void EventProcCBEventMouseDown( NavCBRecPtr ioParams);
58 void EventProcCBStart( NavCBRecPtr ioParams );
59 void EventProcCBPopupMenuSelect( NavCBRecPtr ioParams );
60 void EventProcCBCustomize( NavCBRecPtr ioParams );
61 void EventProcCBAdjustRect( NavCBRecPtr ioParams );
62 bool CheckFile( const wxString &filename , OSType type);
63 void MakeUserDataRec( const wxString& filter);
64
65 wxFileDialog* dialog;
66 int currentfilter ;
67 wxString defaultLocation;
68 wxArrayString extensions ;
69 wxArrayLong filtermactypes ;
70 CFMutableArrayRef menuitems ;
71 wxArrayString name ;
72 bool saveMode ;
73 };
74
75 OpenUserDataRec::OpenUserDataRec( wxFileDialog* d)
76 {
77 dialog = d;
78 saveMode = dialog->HasFdFlag(wxFD_SAVE);
79
80 defaultLocation = dialog->GetDirectory();
81 MakeUserDataRec(dialog->GetWildcard());
82 currentfilter = dialog->GetFilterIndex();
83
84 menuitems = NULL;
85
86 size_t numFilters = extensions.GetCount();
87 if (numFilters)
88 {
89 menuitems = CFArrayCreateMutable( kCFAllocatorDefault ,
90 numFilters , &kCFTypeArrayCallBacks ) ;
91 for ( size_t i = 0 ; i < numFilters ; ++i )
92 {
93 CFArrayAppendValue( menuitems , (CFStringRef) wxCFStringRef( name[i] ) ) ;
94 }
95 }
96
97 }
98
99 void OpenUserDataRec::EventProc(NavEventCallbackMessage inSelector,NavCBRecPtr ioParams)
100 {
101 switch (inSelector)
102 {
103 case kNavCBEvent:
104 EventProcCBEvent(ioParams);
105 break;
106 case kNavCBStart:
107 EventProcCBStart(ioParams);
108 break;
109 case kNavCBPopupMenuSelect:
110 EventProcCBPopupMenuSelect(ioParams);
111 break;
112 case kNavCBCustomize:
113 EventProcCBCustomize(ioParams);
114 break;
115 case kNavCBAdjustRect:
116 EventProcCBAdjustRect(ioParams);
117 break;
118 default:
119 break;
120 }
121 }
122
123 void OpenUserDataRec::EventProcCBEvent(NavCBRecPtr callBackParms)
124 {
125 switch (callBackParms->eventData.eventDataParms.event->what)
126 {
127 case mouseDown:
128 {
129 EventProcCBEventMouseDown(callBackParms);
130 break;
131 }
132 }
133 }
134
135 void OpenUserDataRec::EventProcCBEventMouseDown(NavCBRecPtr callBackParms)
136 {
137 EventRecord *evt = callBackParms->eventData.eventDataParms.event;
138 Point where = evt->where;
139 GlobalToLocal(&where);
140
141 ControlRef whichControl = FindControlUnderMouse(where, callBackParms->window, NULL);
142 if (whichControl != NULL)
143 {
144 ControlKind theKind;
145 GetControlKind(whichControl, &theKind);
146
147 // Moving the focus if we clicked in an editable text control
148 // In this sample, we only have a Clock and an Unicode Edit controls
149 if ((theKind.kind == kControlKindEditUnicodeText) || (theKind.kind == kControlKindClock))
150 {
151 ControlRef currentlyFocusedControl;
152 GetKeyboardFocus(callBackParms->window, &currentlyFocusedControl);
153 if (currentlyFocusedControl != whichControl)
154 SetKeyboardFocus(callBackParms->window, whichControl, kControlFocusNextPart);
155 }
156 HandleControlClick(whichControl, where, evt->modifiers, NULL);
157 }
158 }
159
160 void OpenUserDataRec::EventProcCBStart(NavCBRecPtr ioParams)
161 {
162 if (!defaultLocation.empty())
163 {
164 // Set default location for the modern Navigation APIs
165 // Apple Technical Q&A 1151
166 FSRef theFile;
167 wxMacPathToFSRef(defaultLocation, &theFile);
168 AEDesc theLocation = { typeNull, NULL };
169 if (noErr == ::AECreateDesc(typeFSRef, &theFile, sizeof(FSRef), &theLocation))
170 ::NavCustomControl(ioParams->context, kNavCtlSetLocation, (void *) &theLocation);
171 }
172
173 if( extensions.GetCount() > 0 )
174 {
175 NavMenuItemSpec menuItem;
176 memset( &menuItem, 0, sizeof(menuItem) );
177 menuItem.version = kNavMenuItemSpecVersion;
178 menuItem.menuType = currentfilter;
179 ::NavCustomControl(ioParams->context, kNavCtlSelectCustomType, &menuItem);
180 }
181
182 if (dialog->GetExtraControl())
183 {
184 ControlRef ref = dialog->GetExtraControl()->GetPeer()->GetControlRef();
185 NavCustomControl(ioParams->context, kNavCtlAddControl, ref);
186 }
187
188 }
189
190 void OpenUserDataRec::EventProcCBPopupMenuSelect(NavCBRecPtr ioParams)
191 {
192 NavMenuItemSpec * menu = (NavMenuItemSpec *) ioParams->eventData.eventDataParms.param ;
193 const size_t numFilters = extensions.GetCount();
194
195 if ( menu->menuType < numFilters )
196 {
197 currentfilter = menu->menuType ;
198 if ( saveMode )
199 {
200 int i = menu->menuType ;
201
202 // isolate the first extension string
203 wxString firstExtension = extensions[i].BeforeFirst('|').BeforeFirst(';');
204
205 wxString extension = firstExtension.AfterLast('.') ;
206 wxString sfilename ;
207
208 wxCFStringRef cfString( wxCFRetain( NavDialogGetSaveFileName( ioParams->context ) ) );
209 sfilename = cfString.AsString() ;
210
211 int pos = sfilename.Find('.', true) ;
212 if ( pos != wxNOT_FOUND && extension != wxT("*") )
213 {
214 sfilename = sfilename.Left(pos+1)+extension ;
215 cfString = wxCFStringRef( sfilename , wxFONTENCODING_DEFAULT ) ;
216 NavDialogSetSaveFileName( ioParams->context , cfString ) ;
217 }
218 }
219 }
220 }
221
222 void OpenUserDataRec::EventProcCBCustomize(NavCBRecPtr ioParams)
223 {
224 if (ioParams->customRect.right == 0 && ioParams->customRect.bottom == 0 && dialog->GetExtraControl())
225 {
226 wxSize size = dialog->GetExtraControl()->GetSize();
227 ioParams->customRect.right = size.x;
228 ioParams->customRect.bottom = size.y;
229 }
230 }
231
232 void OpenUserDataRec::EventProcCBAdjustRect(NavCBRecPtr ioParams)
233 {
234 }
235
236 void OpenUserDataRec::MakeUserDataRec( const wxString& filter )
237 {
238 menuitems = NULL ;
239 currentfilter = 0 ;
240 saveMode = false ;
241
242 if ( !filter.empty() )
243 {
244 wxString filter2(filter) ;
245 int filterIndex = 0;
246 bool isName = true ;
247 wxString current ;
248
249 for ( unsigned int i = 0; i < filter2.length() ; i++ )
250 {
251 if ( filter2.GetChar(i) == wxT('|') )
252 {
253 if ( isName )
254 {
255 name.Add( current ) ;
256 }
257 else
258 {
259 extensions.Add( current ) ;
260 ++filterIndex ;
261 }
262
263 isName = !isName ;
264 current = wxEmptyString ;
265 }
266 else
267 {
268 current += filter2.GetChar(i) ;
269 }
270 }
271 // we allow for compatibility reason to have a single filter expression (like *.*) without
272 // an explanatory text, in that case the first part is name and extension at the same time
273
274 wxASSERT_MSG( filterIndex == 0 || !isName , wxT("incorrect format of format string") ) ;
275 if ( current.empty() )
276 extensions.Add( name[filterIndex] ) ;
277 else
278 extensions.Add( current ) ;
279 if ( filterIndex == 0 || isName )
280 name.Add( current ) ;
281
282 ++filterIndex ;
283
284 const size_t extCount = extensions.GetCount();
285 for ( size_t i = 0 ; i < extCount; i++ )
286 {
287 wxUint32 fileType, creator;
288 wxString extension = extensions[i];
289
290 // Remove leading '*'
291 if (extension.length() && (extension.GetChar(0) == '*'))
292 extension = extension.Mid( 1 );
293
294 // Remove leading '.'
295 if (extension.length() && (extension.GetChar(0) == '.'))
296 extension = extension.Mid( 1 );
297
298 if (wxFileName::MacFindDefaultTypeAndCreator( extension, &fileType, &creator ))
299 filtermactypes.Add( (OSType)fileType );
300 else
301 filtermactypes.Add( '****' ); // We'll fail safe if it's not recognized
302 }
303 }
304 }
305
306 bool OpenUserDataRec::CheckFile( const wxString &filename , OSType type)
307 {
308 wxString file(filename) ;
309 file.MakeUpper() ;
310
311 if ( extensions.GetCount() > 0 )
312 {
313 //for ( int i = 0 ; i < data->numfilters ; ++i )
314 int i = currentfilter ;
315 if ( extensions[i].Right(2) == wxT(".*") )
316 return true ;
317
318 {
319 if ( type == (OSType)filtermactypes[i] )
320 return true ;
321
322 wxStringTokenizer tokenizer( extensions[i] , wxT(";") ) ;
323 while ( tokenizer.HasMoreTokens() )
324 {
325 wxString extension = tokenizer.GetNextToken() ;
326 if ( extension.GetChar(0) == '*' )
327 extension = extension.Mid(1) ;
328 extension.MakeUpper();
329
330 if ( file.length() >= extension.length() && extension == file.Right(extension.length() ) )
331 return true ;
332 }
333 }
334
335 return false ;
336 }
337
338 return true ;
339 }
340
341 bool OpenUserDataRec::FilterCallback(
342 AEDesc *theItem,
343 void *info,
344 NavFilterModes filterMode )
345 {
346 if (filterMode == kNavFilteringBrowserList)
347 {
348 // We allow navigation to all folders. For files, we check against the current
349 // filter string.
350 // However, packages should be dealt with like files and not like folders. So
351 // check if a folder is a package before deciding what to do.
352 NavFileOrFolderInfo* theInfo = (NavFileOrFolderInfo*) info ;
353 FSRef fsref;
354
355 if ( theInfo->isFolder )
356 {
357 // check bundle bit (using Finder Services - used by OS9 on some bundles)
358 FSCatalogInfo catalogInfo;
359 if (FSGetCatalogInfo (&fsref, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL) != noErr)
360 return true;
361
362 // Check bundle item (using Launch Services - used by OS-X through info.plist or APP)
363 LSItemInfoRecord lsInfo;
364 if (LSCopyItemInfoForRef(&fsref, kLSRequestBasicFlagsOnly, &lsInfo ) != noErr)
365 return true;
366
367 // If it's not a bundle, then it's a normal folder and it passes our filter
368 FileInfo *fileInfo = (FileInfo *) catalogInfo.finderInfo;
369 if ( !(fileInfo->finderFlags & kHasBundle) &&
370 !(lsInfo.flags & (kLSItemInfoIsApplication | kLSItemInfoIsPackage)) )
371 return true;
372 }
373 else
374 {
375 AECoerceDesc (theItem, typeFSRef, theItem);
376 if ( AEGetDescData (theItem, &fsref, sizeof (FSRef)) == noErr)
377 {
378 wxString file = wxMacFSRefToPath( &fsref ) ;
379 return CheckFile( file , theInfo->fileAndFolder.fileInfo.finderInfo.fdType ) ;
380 }
381 }
382 }
383
384 return true;
385 }
386
387 // end wxmac
388
389 pascal Boolean CrossPlatformFilterCallback(
390 AEDesc *theItem,
391 void *info,
392 void *callBackUD,
393 NavFilterModes filterMode );
394
395 pascal Boolean CrossPlatformFilterCallback(
396 AEDesc *theItem,
397 void *info,
398 void *callBackUD,
399 NavFilterModes filterMode )
400 {
401 OpenUserDataRec* data = (OpenUserDataRec*) callBackUD ;
402 return data->FilterCallback(theItem,info,filterMode);
403 }
404
405 static pascal void NavEventProc(
406 NavEventCallbackMessage inSelector,
407 NavCBRecPtr ioParams,
408 NavCallBackUserData ioUserData );
409
410 static NavEventUPP sStandardNavEventFilter = NewNavEventUPP(NavEventProc);
411
412 static pascal void NavEventProc(
413 NavEventCallbackMessage inSelector,
414 NavCBRecPtr ioParams,
415 NavCallBackUserData ioUserData )
416 {
417 OpenUserDataRec * data = ( OpenUserDataRec *) ioUserData ;
418 data->EventProc(inSelector, ioParams);
419 }
420
421
422 wxFileDialog::wxFileDialog(
423 wxWindow *parent, const wxString& message,
424 const wxString& defaultDir, const wxString& defaultFileName, const wxString& wildCard,
425 long style, const wxPoint& pos, const wxSize& sz, const wxString& name)
426 : wxFileDialogBase(parent, message, defaultDir, defaultFileName, wildCard, style, pos, sz, name)
427 {
428 wxASSERT_MSG( NavServicesAvailable() , wxT("Navigation Services are not running") ) ;
429 }
430
431 int wxFileDialog::ShowModal()
432 {
433 m_paths.Empty();
434 m_fileNames.Empty();
435
436 OSErr err;
437 NavDialogCreationOptions dialogCreateOptions;
438
439 // set default options
440 ::NavGetDefaultDialogCreationOptions(&dialogCreateOptions);
441
442 // this was always unset in the old code
443 dialogCreateOptions.optionFlags &= ~kNavSelectDefaultLocation;
444
445 wxCFStringRef message(m_message, GetFont().GetEncoding());
446 dialogCreateOptions.windowTitle = message;
447
448 wxCFStringRef defaultFileName(m_fileName, GetFont().GetEncoding());
449 dialogCreateOptions.saveFileName = defaultFileName;
450
451 NavDialogRef dialog;
452 NavObjectFilterUPP navFilterUPP = NULL;
453 OpenUserDataRec myData( this );
454
455 dialogCreateOptions.popupExtension = myData.GetMenuItems();
456
457 if (HasFdFlag(wxFD_SAVE))
458 {
459 dialogCreateOptions.optionFlags |= kNavDontAutoTranslate;
460 dialogCreateOptions.optionFlags |= kNavDontAddTranslateItems;
461 if (dialogCreateOptions.popupExtension == NULL)
462 dialogCreateOptions.optionFlags |= kNavNoTypePopup;
463
464 // The extension is important
465 if ( dialogCreateOptions.popupExtension == NULL || CFArrayGetCount(dialogCreateOptions.popupExtension)<2)
466 dialogCreateOptions.optionFlags |= kNavPreserveSaveFileExtension;
467
468 if (!(m_windowStyle & wxFD_OVERWRITE_PROMPT))
469 dialogCreateOptions.optionFlags |= kNavDontConfirmReplacement;
470
471 err = ::NavCreatePutFileDialog(
472 &dialogCreateOptions,
473 kNavGenericSignature, // Suppresses the 'Default' (top) menu item
474 kNavGenericSignature,
475 sStandardNavEventFilter,
476 &myData, // for defaultLocation
477 &dialog );
478 }
479 else
480 {
481 // let the user select bundles/programs in dialogs
482 dialogCreateOptions.optionFlags |= kNavSupportPackages;
483
484 navFilterUPP = NewNavObjectFilterUPP(CrossPlatformFilterCallback);
485 err = ::NavCreateGetFileDialog(
486 &dialogCreateOptions,
487 NULL, // NavTypeListHandle
488 sStandardNavEventFilter,
489 NULL, // NavPreviewUPP
490 navFilterUPP,
491 (void *) &myData, // inClientData
492 &dialog );
493 }
494
495 wxNonOwnedWindow::Create( GetParent(), NavDialogGetWindow(dialog) );
496
497 if (HasExtraControlCreator())
498 {
499 CreateExtraControl();
500 }
501
502 if (err == noErr)
503 err = ::NavDialogRun(dialog);
504
505 // clean up filter related data, etc.
506 if (navFilterUPP)
507 ::DisposeNavObjectFilterUPP(navFilterUPP);
508
509 if (err != noErr)
510 {
511 ::NavDialogDispose(dialog);
512 return wxID_CANCEL;
513 }
514
515 NavReplyRecord navReply;
516 err = ::NavDialogGetReply(dialog, &navReply);
517 if (err == noErr && navReply.validRecord)
518 {
519 AEKeyword theKeyword;
520 DescType actualType;
521 Size actualSize;
522 FSRef theFSRef;
523 wxString thePath ;
524 long count;
525
526 m_filterIndex = myData.GetCurrentFilter();
527 ::AECountItems( &navReply.selection, &count );
528 for (long i = 1; i <= count; ++i)
529 {
530 err = ::AEGetNthPtr(
531 &(navReply.selection), i, typeFSRef, &theKeyword, &actualType,
532 &theFSRef, sizeof(theFSRef), &actualSize );
533 if (err != noErr)
534 break;
535
536 if (HasFdFlag(wxFD_SAVE))
537 thePath = wxMacFSRefToPath( &theFSRef, navReply.saveFileName );
538 else
539 thePath = wxMacFSRefToPath( &theFSRef );
540
541 if (!thePath)
542 {
543 ::NavDisposeReply(&navReply);
544 ::NavDialogDispose(dialog);
545 return wxID_CANCEL;
546 }
547
548 m_path = thePath;
549 m_paths.Add(m_path);
550 m_fileName = wxFileNameFromPath(m_path);
551 m_fileNames.Add(m_fileName);
552 }
553
554 // set these to the first hit
555 m_path = m_paths[0];
556 m_fileName = wxFileNameFromPath(m_path);
557 m_dir = wxPathOnly(m_path);
558 }
559
560 ::NavDisposeReply(&navReply);
561 ::NavDialogDispose(dialog);
562
563 return (err == noErr) ? wxID_OK : wxID_CANCEL;
564 }
565
566 bool wxFileDialog::SupportsExtraControl() const
567 {
568 return true;
569 }
570
571 #endif // wxUSE_FILEDLG
572