wx/wxprec.h already includes wx/defs.h (with other minor cleaning).
[wxWidgets.git] / src / mac / classic / filedlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/mac/classic/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 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #include "wx/app.h"
19 #include "wx/utils.h"
20 #include "wx/dialog.h"
21 #include "wx/filedlg.h"
22 #include "wx/intl.h"
23 #include "wx/tokenzr.h"
24 #include "wx/filename.h"
25
26 #ifndef __DARWIN__
27 #include "PLStringFuncs.h"
28 #endif
29
30 IMPLEMENT_CLASS(wxFileDialog, wxFileDialogBase)
31
32 // begin wxmac
33
34 #include "wx/mac/private.h"
35
36 #include <Navigation.h>
37
38 #ifdef __DARWIN__
39 # include "MoreFilesX.h"
40 #else
41 # include "MoreFiles.h"
42 # include "MoreFilesExtras.h"
43 #endif
44
45 extern bool gUseNavServices ;
46
47 // the data we need to pass to our standard file hook routine
48 // includes a pointer to the dialog, a pointer to the standard
49 // file reply record (so we can inspect the current selection)
50 // and a copy of the "previous" file spec of the reply record
51 // so we can see if the selection has changed
52
53 struct OpenUserDataRec {
54 int currentfilter ;
55 bool saveMode ;
56 wxArrayString name ;
57 wxArrayString extensions ;
58 wxArrayLong filtermactypes ;
59 wxString defaultLocation;
60 #if TARGET_CARBON
61 CFArrayRef menuitems ;
62 #else
63 NavMenuItemSpecArrayHandle menuitems ;
64 #endif
65 };
66
67 typedef struct OpenUserDataRec
68 OpenUserDataRec, *OpenUserDataRecPtr;
69
70 static pascal void NavEventProc(
71 NavEventCallbackMessage inSelector,
72 NavCBRecPtr ioParams,
73 NavCallBackUserData ioUserData);
74
75 #if TARGET_CARBON
76 static NavEventUPP sStandardNavEventFilter = NewNavEventUPP(NavEventProc);
77 #else
78 static NavEventUPP sStandardNavEventFilter = NewNavEventProc(NavEventProc);
79 #endif
80
81 static pascal void
82 NavEventProc(
83 NavEventCallbackMessage inSelector,
84 NavCBRecPtr ioParams,
85 NavCallBackUserData ioUserData )
86 {
87 OpenUserDataRec * data = ( OpenUserDataRec *) ioUserData ;
88 if (inSelector == kNavCBEvent) {
89 #if TARGET_CARBON
90 #else
91 wxTheApp->MacHandleOneEvent(ioParams->eventData.eventDataParms.event);
92 #endif
93 }
94 else if ( inSelector == kNavCBStart )
95 {
96 #if TARGET_CARBON
97 if (data && !(data->defaultLocation).IsEmpty())
98 {
99 // Set default location for the modern Navigation APIs
100 // Apple Technical Q&A 1151
101 FSSpec theFSSpec;
102 wxMacFilename2FSSpec(data->defaultLocation, &theFSSpec);
103 AEDesc theLocation = {typeNull, NULL};
104 if (noErr == ::AECreateDesc(typeFSS, &theFSSpec, sizeof(FSSpec), &theLocation))
105 ::NavCustomControl(ioParams->context, kNavCtlSetLocation, (void *) &theLocation);
106 }
107 #else
108 if ( data->menuitems )
109 NavCustomControl(ioParams->context, kNavCtlSelectCustomType, &(*data->menuitems)[data->currentfilter]);
110 #endif
111 }
112 else if ( inSelector == kNavCBPopupMenuSelect )
113 {
114 NavMenuItemSpec * menu = (NavMenuItemSpec *) ioParams->eventData.eventDataParms.param ;
115 #if TARGET_CARBON
116 #else
117 if ( menu->menuCreator == 'WXNG' )
118 #endif
119 {
120 data->currentfilter = menu->menuType ;
121 if ( data->saveMode )
122 {
123 int i = menu->menuType ;
124 wxString extension = data->extensions[i].AfterLast('.') ;
125 extension.MakeLower() ;
126 wxString sfilename ;
127
128 #if TARGET_CARBON
129 wxMacCFStringHolder cfString( NavDialogGetSaveFileName( ioParams->context ) , false );
130 sfilename = cfString.AsString() ;
131 #else
132 Str255 filename ;
133 // get the current filename
134 NavCustomControl(ioParams->context, kNavCtlGetEditFileName, &filename);
135 sfilename = wxMacMakeStringFromPascal( filename ) ;
136 #endif
137
138 int pos = sfilename.Find('.', true) ;
139 if ( pos != wxNOT_FOUND )
140 {
141 sfilename = sfilename.Left(pos+1)+extension ;
142 #if TARGET_CARBON
143 cfString.Assign( sfilename , wxFONTENCODING_DEFAULT ) ;
144 NavDialogSetSaveFileName( ioParams->context , cfString ) ;
145 #else
146 wxMacStringToPascal( sfilename , filename ) ;
147 NavCustomControl(ioParams->context, kNavCtlSetEditFileName, &filename);
148 #endif
149 }
150 }
151 }
152 }
153 }
154
155
156 void MakeUserDataRec(OpenUserDataRec *myData , const wxString& filter )
157 {
158 myData->menuitems = NULL ;
159 myData->currentfilter = 0 ;
160 myData->saveMode = false ;
161
162 if ( filter && filter[0] )
163 {
164 wxString filter2(filter) ;
165 int filterIndex = 0;
166 bool isName = true ;
167 wxString current ;
168 for( unsigned int i = 0; i < filter2.Len() ; i++ )
169 {
170 if( filter2.GetChar(i) == wxT('|') )
171 {
172 if( isName ) {
173 myData->name.Add( current ) ;
174 }
175 else {
176 myData->extensions.Add( current.MakeUpper() ) ;
177 ++filterIndex ;
178 }
179 isName = !isName ;
180 current = wxEmptyString ;
181 }
182 else
183 {
184 current += filter2.GetChar(i) ;
185 }
186 }
187 // we allow for compatibility reason to have a single filter expression (like *.*) without
188 // an explanatory text, in that case the first part is name and extension at the same time
189
190 wxASSERT_MSG( filterIndex == 0 || !isName , wxT("incorrect format of format string") ) ;
191 if ( current.empty() )
192 myData->extensions.Add( myData->name[filterIndex] ) ;
193 else
194 myData->extensions.Add( current.MakeUpper() ) ;
195 if ( filterIndex == 0 || isName )
196 myData->name.Add( current.MakeUpper() ) ;
197
198 ++filterIndex ;
199
200 const size_t extCount = myData->extensions.GetCount();
201 for ( size_t i = 0 ; i < extCount; i++ )
202 {
203 wxUint32 fileType;
204 wxUint32 creator;
205 wxString extension = myData->extensions[i];
206
207 if (extension.GetChar(0) == '*')
208 extension = extension.Mid(1); // Remove leading *
209
210 if (extension.GetChar(0) == '.')
211 {
212 extension = extension.Mid(1); // Remove leading .
213 }
214
215 if (wxFileName::MacFindDefaultTypeAndCreator( extension, &fileType, &creator ))
216 {
217 myData->filtermactypes.Add( (OSType)fileType );
218 }
219 else
220 {
221 myData->filtermactypes.Add( '****' ) ; // We'll fail safe if it's not recognized
222 }
223 }
224 }
225 }
226
227 static Boolean CheckFile( const wxString &filename , OSType type , OpenUserDataRecPtr data)
228 {
229 wxString file(filename) ;
230 file.MakeUpper() ;
231
232 if ( data->extensions.GetCount() > 0 )
233 {
234 //for ( int i = 0 ; i < data->numfilters ; ++i )
235 int i = data->currentfilter ;
236 if ( data->extensions[i].Right(2) == wxT(".*") )
237 return true ;
238
239 {
240 if ( type == (OSType)data->filtermactypes[i] )
241 return true ;
242
243 wxStringTokenizer tokenizer( data->extensions[i] , wxT(";") ) ;
244 while( tokenizer.HasMoreTokens() )
245 {
246 wxString extension = tokenizer.GetNextToken() ;
247 if ( extension.GetChar(0) == '*' )
248 extension = extension.Mid(1) ;
249
250 if ( file.Len() >= extension.Len() && extension == file.Right(extension.Len() ) )
251 return true ;
252 }
253 }
254 return false ;
255 }
256 return true ;
257 }
258
259 #ifndef __DARWIN__
260 static pascal Boolean CrossPlatformFileFilter(CInfoPBPtr myCInfoPBPtr, void *dataPtr)
261 {
262 OpenUserDataRecPtr data = (OpenUserDataRecPtr) dataPtr ;
263 // return true if this item is invisible or a file
264
265 Boolean visibleFlag;
266 Boolean folderFlag;
267
268 visibleFlag = ! (myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible);
269 folderFlag = (myCInfoPBPtr->hFileInfo.ioFlAttrib & 0x10);
270
271 // because the semantics of the filter proc are "true means don't show
272 // it" we need to invert the result that we return
273
274 if ( !visibleFlag )
275 return true ;
276
277 if ( !folderFlag )
278 {
279 wxString file = wxMacMakeStringFromPascal( myCInfoPBPtr->hFileInfo.ioNamePtr ) ;
280 return !CheckFile( file , myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdType , data ) ;
281 }
282
283 return false ;
284 }
285 #endif
286
287 // end wxmac
288
289 wxFileDialog::wxFileDialog(wxWindow *parent, const wxString& message,
290 const wxString& defaultDir, const wxString& defaultFileName, const wxString& wildCard,
291 long style, const wxPoint& pos)
292 :wxFileDialogBase(parent, message, defaultDir, defaultFileName, wildCard, style, pos)
293 {
294 wxASSERT_MSG( NavServicesAvailable() , wxT("Navigation Services are not running") ) ;
295 }
296
297 pascal Boolean CrossPlatformFilterCallback (
298 AEDesc *theItem,
299 void *info,
300 void *callBackUD,
301 NavFilterModes filterMode
302 )
303 {
304 bool display = true;
305 OpenUserDataRecPtr data = (OpenUserDataRecPtr) callBackUD ;
306
307 if (filterMode == kNavFilteringBrowserList)
308 {
309 NavFileOrFolderInfo* theInfo = (NavFileOrFolderInfo*) info ;
310 if ( !theInfo->isFolder )
311 {
312 if (theItem->descriptorType == typeFSS )
313 {
314 FSSpec spec;
315 memcpy( &spec , *theItem->dataHandle , sizeof(FSSpec) ) ;
316 wxString file = wxMacMakeStringFromPascal( spec.name ) ;
317 display = CheckFile( file , theInfo->fileAndFolder.fileInfo.finderInfo.fdType , data ) ;
318 }
319 #if TARGET_CARBON
320 else if ( theItem->descriptorType == typeFSRef )
321 {
322 FSRef fsref ;
323 memcpy( &fsref , *theItem->dataHandle , sizeof(FSRef) ) ;
324
325
326
327 CFURLRef fullURLRef;
328 fullURLRef = ::CFURLCreateFromFSRef(NULL, &fsref);
329 #ifdef __UNIX__
330 CFURLPathStyle pathstyle = kCFURLPOSIXPathStyle;
331 #else
332 CFURLPathStyle pathstyle = kCFURLHFSPathStyle;
333 #endif
334 CFStringRef cfString = CFURLCopyFileSystemPath(fullURLRef, pathstyle);
335 ::CFRelease( fullURLRef ) ;
336 wxString file = wxMacCFStringHolder(cfString).AsString(wxFont::GetDefaultEncoding());
337
338 display = CheckFile( file , theInfo->fileAndFolder.fileInfo.finderInfo.fdType , data ) ;
339 }
340 #endif
341 }
342 }
343
344 return display;
345 }
346
347 int wxFileDialog::ShowModal()
348 {
349 #if TARGET_CARBON
350 OSErr err;
351 NavDialogCreationOptions dialogCreateOptions;
352 // set default options
353 ::NavGetDefaultDialogCreationOptions(&dialogCreateOptions);
354
355 // this was always unset in the old code
356 dialogCreateOptions.optionFlags &= ~kNavSelectDefaultLocation;
357
358 wxMacCFStringHolder message(m_message, m_font.GetEncoding());
359 dialogCreateOptions.windowTitle = message;
360
361 wxMacCFStringHolder defaultFileName(m_fileName, m_font.GetEncoding());
362 dialogCreateOptions.saveFileName = defaultFileName;
363
364
365 NavDialogRef dialog;
366 NavObjectFilterUPP navFilterUPP = NULL;
367 CFArrayRef cfArray = NULL; // for popupExtension
368 OpenUserDataRec myData;
369 myData.defaultLocation = m_dir;
370
371 if (m_dialogStyle & wxSAVE)
372 {
373 dialogCreateOptions.optionFlags |= kNavNoTypePopup;
374 dialogCreateOptions.optionFlags |= kNavDontAutoTranslate;
375 dialogCreateOptions.optionFlags |= kNavDontAddTranslateItems;
376
377 // The extension is important
378 dialogCreateOptions.optionFlags |= kNavPreserveSaveFileExtension;
379
380 err = ::NavCreatePutFileDialog(&dialogCreateOptions,
381 'TEXT',
382 'TEXT',
383 sStandardNavEventFilter,
384 &myData, // for defaultLocation
385 &dialog);
386 }
387 else
388 {
389 MakeUserDataRec(&myData , m_wildCard);
390 size_t numfilters = myData.extensions.GetCount();
391 if (numfilters > 0)
392 {
393 CFMutableArrayRef popup = CFArrayCreateMutable( kCFAllocatorDefault ,
394 numfilters , &kCFTypeArrayCallBacks ) ;
395 dialogCreateOptions.popupExtension = popup ;
396 myData.menuitems = dialogCreateOptions.popupExtension ;
397 for ( size_t i = 0 ; i < numfilters ; ++i )
398 {
399 CFArrayAppendValue( popup , (CFStringRef) wxMacCFStringHolder( myData.name[i] , m_font.GetEncoding() ) ) ;
400 }
401 }
402
403 navFilterUPP = NewNavObjectFilterUPP(CrossPlatformFilterCallback);
404 err = ::NavCreateGetFileDialog(&dialogCreateOptions,
405 NULL, // NavTypeListHandle
406 sStandardNavEventFilter,
407 NULL, // NavPreviewUPP
408 navFilterUPP,
409 (void *) &myData, // inClientData
410 &dialog);
411 }
412
413 if (err == noErr)
414 err = ::NavDialogRun(dialog);
415
416 // clean up filter related data, etc.
417 if (navFilterUPP)
418 ::DisposeNavObjectFilterUPP(navFilterUPP);
419 if (cfArray)
420 ::CFRelease(cfArray);
421
422 if (err != noErr)
423 return wxID_CANCEL;
424
425 NavReplyRecord navReply;
426 err = ::NavDialogGetReply(dialog, &navReply);
427 if (err == noErr && navReply.validRecord)
428 {
429 AEKeyword theKeyword;
430 DescType actualType;
431 Size actualSize;
432 FSRef theFSRef;
433 wxString thePath ;
434 long count;
435 ::AECountItems(&navReply.selection , &count);
436 for (long i = 1; i <= count; ++i)
437 {
438 err = ::AEGetNthPtr(&(navReply.selection), i, typeFSRef, &theKeyword, &actualType,
439 &theFSRef, sizeof(theFSRef), &actualSize);
440 if (err != noErr)
441 break;
442
443 CFURLRef fullURLRef;
444 if (m_dialogStyle & wxSAVE)
445 {
446 CFURLRef parentURLRef = ::CFURLCreateFromFSRef(NULL, &theFSRef);
447
448 if (parentURLRef)
449 {
450 fullURLRef =
451 ::CFURLCreateCopyAppendingPathComponent(NULL,
452 parentURLRef,
453 navReply.saveFileName,
454 false);
455 ::CFRelease(parentURLRef);
456 }
457 }
458 else
459 {
460 fullURLRef = ::CFURLCreateFromFSRef(NULL, &theFSRef);
461 }
462 #ifdef __UNIX__
463 CFURLPathStyle pathstyle = kCFURLPOSIXPathStyle;
464 #else
465 CFURLPathStyle pathstyle = kCFURLHFSPathStyle;
466 #endif
467 CFStringRef cfString = CFURLCopyFileSystemPath(fullURLRef, pathstyle);
468 thePath = wxMacCFStringHolder(cfString).AsString(m_font.GetEncoding());
469 if (!thePath)
470 {
471 ::NavDisposeReply(&navReply);
472 return wxID_CANCEL;
473 }
474 m_path = thePath;
475 m_paths.Add(m_path);
476 m_fileName = wxFileNameFromPath(m_path);
477 m_fileNames.Add(m_fileName);
478 }
479 // set these to the first hit
480 m_path = m_paths[0];
481 m_fileName = wxFileNameFromPath(m_path);
482 m_dir = wxPathOnly(m_path);
483 }
484 ::NavDisposeReply(&navReply);
485
486 return (err == noErr) ? wxID_OK : wxID_CANCEL;
487 #else // TARGET_CARBON
488
489 NavDialogOptions mNavOptions;
490 NavObjectFilterUPP mNavFilterUPP = NULL;
491 NavPreviewUPP mNavPreviewUPP = NULL ;
492 NavReplyRecord mNavReply;
493 AEDesc mDefaultLocation ;
494 bool mSelectDefault = false ;
495 OSStatus err = noErr ;
496 // setup dialog
497
498 mNavFilterUPP = nil;
499 mNavPreviewUPP = nil;
500 mSelectDefault = false;
501 mDefaultLocation.descriptorType = typeNull;
502 mDefaultLocation.dataHandle = nil;
503
504 NavGetDefaultDialogOptions(&mNavOptions);
505 wxMacStringToPascal( m_message , (StringPtr)mNavOptions.message ) ;
506 wxMacStringToPascal( m_fileName , (StringPtr)mNavOptions.savedFileName ) ;
507
508 // Set default location, the location
509 // that's displayed when the dialog
510 // first appears
511
512 FSSpec location ;
513 wxMacFilename2FSSpec( m_dir , &location ) ;
514
515 err = ::AECreateDesc(typeFSS, &location, sizeof(FSSpec), &mDefaultLocation );
516
517 if ( mDefaultLocation.dataHandle )
518 {
519 if (mSelectDefault)
520 {
521 mNavOptions.dialogOptionFlags |= kNavSelectDefaultLocation;
522 } else {
523 mNavOptions.dialogOptionFlags &= ~kNavSelectDefaultLocation;
524 }
525 }
526
527 memset( &mNavReply , 0 , sizeof( mNavReply ) ) ;
528 mNavReply.validRecord = false;
529 mNavReply.replacing = false;
530 mNavReply.isStationery = false;
531 mNavReply.translationNeeded = false;
532 mNavReply.selection.descriptorType = typeNull;
533 mNavReply.selection.dataHandle = nil;
534 mNavReply.keyScript = smSystemScript;
535 mNavReply.fileTranslation = nil;
536 mNavReply.version = kNavReplyRecordVersion ;
537
538 // zero all data
539
540 m_path = wxEmptyString ;
541 m_fileName = wxEmptyString ;
542 m_paths.Empty();
543 m_fileNames.Empty();
544
545 OpenUserDataRec myData;
546 MakeUserDataRec( &myData , m_wildCard ) ;
547 myData.currentfilter = m_filterIndex ;
548 if ( myData.extensions.GetCount() > 0 )
549 {
550 mNavOptions.popupExtension = (NavMenuItemSpecArrayHandle) NewHandle( sizeof( NavMenuItemSpec ) * myData.extensions.GetCount() ) ;
551 myData.menuitems = mNavOptions.popupExtension ;
552 for ( size_t i = 0 ; i < myData.extensions.GetCount() ; ++i )
553 {
554 (*mNavOptions.popupExtension)[i].version = kNavMenuItemSpecVersion ;
555 (*mNavOptions.popupExtension)[i].menuCreator = 'WXNG' ;
556 // TODO : according to the new docs -1 to 10 are reserved for the OS
557 (*mNavOptions.popupExtension)[i].menuType = i ;
558 wxMacStringToPascal( myData.name[i] , (StringPtr)(*mNavOptions.popupExtension)[i].menuItemName ) ;
559 }
560 }
561 if ( m_dialogStyle & wxSAVE )
562 {
563 myData.saveMode = true ;
564
565 mNavOptions.dialogOptionFlags |= kNavDontAutoTranslate ;
566 mNavOptions.dialogOptionFlags |= kNavDontAddTranslateItems ;
567
568 err = ::NavPutFile(
569 &mDefaultLocation,
570 &mNavReply,
571 &mNavOptions,
572 sStandardNavEventFilter ,
573 NULL,
574 kNavGenericSignature,
575 &myData); // User Data
576 m_filterIndex = myData.currentfilter ;
577 }
578 else
579 {
580 myData.saveMode = false ;
581
582 mNavFilterUPP = NewNavObjectFilterUPP( CrossPlatformFilterCallback ) ;
583 if ( m_dialogStyle & wxMULTIPLE )
584 mNavOptions.dialogOptionFlags |= kNavAllowMultipleFiles ;
585 else
586 mNavOptions.dialogOptionFlags &= ~kNavAllowMultipleFiles ;
587
588 err = ::NavGetFile(
589 &mDefaultLocation,
590 &mNavReply,
591 &mNavOptions,
592 sStandardNavEventFilter ,
593 mNavPreviewUPP,
594 mNavFilterUPP,
595 NULL ,
596 &myData);
597 m_filterIndex = myData.currentfilter ;
598 }
599
600 DisposeNavObjectFilterUPP(mNavFilterUPP);
601 if ( mDefaultLocation.dataHandle != nil )
602 {
603 ::AEDisposeDesc(&mDefaultLocation);
604 }
605
606 if ( (err != noErr) && (err != userCanceledErr) ) {
607 return wxID_CANCEL ;
608 }
609
610 if (mNavReply.validRecord)
611 {
612 FSSpec outFileSpec ;
613 AEDesc specDesc ;
614 AEKeyword keyWord ;
615
616 long count ;
617 ::AECountItems( &mNavReply.selection , &count ) ;
618 for ( long i = 1 ; i <= count ; ++i )
619 {
620 OSErr err = ::AEGetNthDesc( &mNavReply.selection , i , typeFSS, &keyWord , &specDesc);
621 if ( err != noErr )
622 {
623 m_path = wxEmptyString ;
624 return wxID_CANCEL ;
625 }
626 outFileSpec = **(FSSpec**) specDesc.dataHandle;
627 if (specDesc.dataHandle != nil) {
628 ::AEDisposeDesc(&specDesc);
629 }
630 m_path = wxMacFSSpec2MacFilename( &outFileSpec ) ;
631
632 m_paths.Add( m_path ) ;
633 m_fileName = wxFileNameFromPath(m_path);
634 m_fileNames.Add(m_fileName);
635 }
636 // set these to the first hit
637 m_path = m_paths[ 0 ] ;
638 m_fileName = wxFileNameFromPath(m_path);
639 m_dir = wxPathOnly(m_path);
640 NavDisposeReply( &mNavReply ) ;
641 return wxID_OK ;
642 }
643 return wxID_CANCEL;
644 #endif // TARGET_CARBON
645 }