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