1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/mac/carbon/filedlg.cpp
3// Purpose:     wxFileDialog
4// Author:      Stefan Csomor
5// Modified by:
6// Created:     1998-01-01
7// RCS-ID:      $Id: filedlg.cpp 62110 2009-09-25 07:52:08Z JS $
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/mac/private.h"
29
30#ifndef __DARWIN__
31    #include <Navigation.h>
32    #include "PLStringFuncs.h"
33#endif
34
35IMPLEMENT_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
43struct OpenUserDataRec
44{
45  int                currentfilter ;
46  bool               saveMode ;
47  wxArrayString      name ;
48  wxArrayString      extensions ;
49  wxArrayLong        filtermactypes ;
50  wxString           defaultLocation;
51  CFArrayRef         menuitems ;
52};
53
54typedef struct OpenUserDataRec
55OpenUserDataRec, *OpenUserDataRecPtr;
56
57static pascal void NavEventProc(
58    NavEventCallbackMessage inSelector,
59    NavCBRecPtr ioParams,
60    NavCallBackUserData ioUserData );
61
62static NavEventUPP sStandardNavEventFilter = NewNavEventUPP(NavEventProc);
63
64static pascal void NavEventProc(
65    NavEventCallbackMessage inSelector,
66    NavCBRecPtr ioParams,
67    NavCallBackUserData ioUserData )
68{
69    OpenUserDataRec * data = ( OpenUserDataRec *) ioUserData ;
70    if (inSelector == kNavCBEvent)
71    {
72    }
73    else if ( inSelector == kNavCBStart )
74    {
75        if (data && !(data->defaultLocation).empty())
76        {
77            // Set default location for the modern Navigation APIs
78            // Apple Technical Q&A 1151
79            FSRef theFile;
80            wxMacPathToFSRef(data->defaultLocation, &theFile);
81            AEDesc theLocation = { typeNull, NULL };
82            if (noErr == ::AECreateDesc(typeFSRef, &theFile, sizeof(FSRef), &theLocation))
83                ::NavCustomControl(ioParams->context, kNavCtlSetLocation, (void *) &theLocation);
84        }
85
86        if( data->extensions.GetCount() > 0 )
87        {
88            NavMenuItemSpec  menuItem;
89            memset( &menuItem, 0, sizeof(menuItem) );
90            menuItem.version = kNavMenuItemSpecVersion;
91            menuItem.menuType = data->currentfilter;
92            ::NavCustomControl(ioParams->context, kNavCtlSelectCustomType, &menuItem);
93        }
94    }
95    else if ( inSelector == kNavCBPopupMenuSelect )
96    {
97        NavMenuItemSpec * menu = (NavMenuItemSpec *) ioParams->eventData.eventDataParms.param ;
98        const size_t numFilters = data->extensions.GetCount();
99
100        if ( menu->menuType < numFilters )
101        {
102            data->currentfilter = menu->menuType ;
103            if ( data->saveMode )
104            {
105                int i = menu->menuType ;
106                wxString extension =  data->extensions[i].AfterLast('.') ;
107                wxString sfilename ;
108
109                wxMacCFStringHolder cfString( NavDialogGetSaveFileName( ioParams->context ) , false  );
110                sfilename = cfString.AsString() ;
111
112                int pos = sfilename.Find('.', true) ;
113                if ( pos != wxNOT_FOUND && extension != wxT("*") )
114                {
115                    sfilename = sfilename.Left(pos+1)+extension ;
116                    cfString.Assign( sfilename , wxFONTENCODING_DEFAULT ) ;
117                    NavDialogSetSaveFileName( ioParams->context , cfString ) ;
118                }
119            }
120        }
121    }
122}
123
124void MakeUserDataRec(OpenUserDataRec *myData , const wxString& filter )
125{
126    myData->menuitems = NULL ;
127    myData->currentfilter = 0 ;
128    myData->saveMode = false ;
129
130    if ( filter && filter[0] )
131    {
132        wxString filter2(filter) ;
133        int filterIndex = 0;
134        bool isName = true ;
135        wxString current ;
136
137        for ( unsigned int i = 0; i < filter2.length() ; i++ )
138        {
139            if ( filter2.GetChar(i) == wxT('|') )
140            {
141                if ( isName )
142                {
143                    myData->name.Add( current ) ;
144                }
145                else
146                {
147                    myData->extensions.Add( current ) ;
148                    ++filterIndex ;
149                }
150
151                isName = !isName ;
152                current = wxEmptyString ;
153            }
154            else
155            {
156                current += filter2.GetChar(i) ;
157            }
158        }
159        // we allow for compatibility reason to have a single filter expression (like *.*) without
160        // an explanatory text, in that case the first part is name and extension at the same time
161
162        wxASSERT_MSG( filterIndex == 0 || !isName , wxT("incorrect format of format string") ) ;
163        if ( current.empty() )
164            myData->extensions.Add( myData->name[filterIndex] ) ;
165        else
166            myData->extensions.Add( current ) ;
167        if ( filterIndex == 0 || isName )
168            myData->name.Add( current ) ;
169
170        ++filterIndex ;
171
172        const size_t extCount = myData->extensions.GetCount();
173        for ( size_t i = 0 ; i < extCount; i++ )
174        {
175            wxUint32 fileType, creator;
176            wxString extension = myData->extensions[i];
177
178            // Remove leading '*'
179            if (extension.length() && (extension.GetChar(0) == '*'))
180                extension = extension.Mid( 1 );
181
182            // Remove leading '.'
183            if (extension.length() && (extension.GetChar(0) == '.'))
184                extension = extension.Mid( 1 );
185
186            if (wxFileName::MacFindDefaultTypeAndCreator( extension, &fileType, &creator ))
187                myData->filtermactypes.Add( (OSType)fileType );
188            else
189                myData->filtermactypes.Add( '****' ); // We'll fail safe if it's not recognized
190        }
191    }
192}
193
194static Boolean CheckFile( const wxString &filename , OSType type , OpenUserDataRecPtr data)
195{
196    wxString file(filename) ;
197    file.MakeUpper() ;
198
199    if ( data->extensions.GetCount() > 0 )
200    {
201        //for ( int i = 0 ; i < data->numfilters ; ++i )
202        int i = data->currentfilter ;
203        if ( data->extensions[i].Right(2) == wxT(".*") )
204            return true ;
205
206        {
207            if ( type == (OSType)data->filtermactypes[i] )
208                return true ;
209
210            wxStringTokenizer tokenizer( data->extensions[i] , wxT(";") ) ;
211            while ( tokenizer.HasMoreTokens() )
212            {
213                wxString extension = tokenizer.GetNextToken() ;
214                if ( extension.GetChar(0) == '*' )
215                    extension = extension.Mid(1) ;
216                extension.MakeUpper();
217
218                if ( file.length() >= extension.length() && extension == file.Right(extension.length() ) )
219                    return true ;
220            }
221        }
222
223        return false ;
224    }
225
226    return true ;
227}
228
229// end wxmac
230
231wxFileDialog::wxFileDialog(
232    wxWindow *parent, const wxString& message,
233    const wxString& defaultDir, const wxString& defaultFileName, const wxString& wildCard,
234    long style, const wxPoint& pos, const wxSize& sz, const wxString& name)
235    : wxFileDialogBase(parent, message, defaultDir, defaultFileName, wildCard, style, pos, sz, name)
236{
237    wxASSERT_MSG( NavServicesAvailable() , wxT("Navigation Services are not running") ) ;
238}
239
240pascal Boolean CrossPlatformFilterCallback(
241    AEDesc *theItem,
242    void *info,
243    void *callBackUD,
244    NavFilterModes filterMode )
245{
246    bool display = true;
247    OpenUserDataRecPtr data = (OpenUserDataRecPtr) callBackUD ;
248
249    if (filterMode == kNavFilteringBrowserList)
250    {
251        NavFileOrFolderInfo* theInfo = (NavFileOrFolderInfo*) info ;
252        if ( !theInfo->isFolder )
253        {
254            AECoerceDesc (theItem, typeFSRef, theItem);
255
256            FSRef fsref ;
257            if ( AEGetDescData (theItem, &fsref, sizeof (FSRef)) == noErr )
258            {
259#if 1
260                memcpy( &fsref , *theItem->dataHandle , sizeof(FSRef) ) ;
261                wxString file = wxMacFSRefToPath( &fsref ) ;
262                display = CheckFile( file , theInfo->fileAndFolder.fileInfo.finderInfo.fdType , data ) ;
263#else
264                CFStringRef itemUTI = NULL;
265                OSStatus status = LSCopyItemAttribute (&fsref, kLSRolesAll, kLSItemContentType, (CFTypeRef*)&itemUTI);
266                if (status == noErr)
267                {
268                    display = UTTypeConformsTo (itemUTI, CFSTR("public.text") );
269                    CFRelease (itemUTI);
270                }
271#endif
272            }
273        }
274    }
275
276    return display;
277}
278
279int wxFileDialog::ShowModal()
280{
281    m_paths.Empty();
282    m_fileNames.Empty();
283
284    OSErr err;
285    NavDialogCreationOptions dialogCreateOptions;
286
287    // set default options
288    ::NavGetDefaultDialogCreationOptions(&dialogCreateOptions);
289
290    // this was always unset in the old code
291    dialogCreateOptions.optionFlags &= ~kNavSelectDefaultLocation;
292
293    wxMacCFStringHolder message(m_message, GetFont().GetEncoding());
294    dialogCreateOptions.windowTitle = message;
295
296    wxMacCFStringHolder defaultFileName(m_fileName, GetFont().GetEncoding());
297    dialogCreateOptions.saveFileName = defaultFileName;
298
299
300    NavDialogRef dialog;
301    NavObjectFilterUPP navFilterUPP = NULL;
302    OpenUserDataRec myData;
303    myData.defaultLocation = m_dir;
304
305    MakeUserDataRec(&myData , m_wildCard);
306    myData.currentfilter = m_filterIndex;
307    size_t numFilters = myData.extensions.GetCount();
308    if (numFilters)
309    {
310        CFMutableArrayRef popup = CFArrayCreateMutable( kCFAllocatorDefault ,
311            numFilters , &kCFTypeArrayCallBacks ) ;
312        dialogCreateOptions.popupExtension = popup ;
313        myData.menuitems = dialogCreateOptions.popupExtension ;
314        for ( size_t i = 0 ; i < numFilters ; ++i )
315        {
316            CFArrayAppendValue( popup , (CFStringRef) wxMacCFStringHolder( myData.name[i] , GetFont().GetEncoding() ) ) ;
317        }
318    }
319
320    if (HasFdFlag(wxFD_SAVE))
321    {
322        myData.saveMode = true;
323
324        dialogCreateOptions.optionFlags |= kNavDontAutoTranslate;
325        dialogCreateOptions.optionFlags |= kNavDontAddTranslateItems;
326        if (!numFilters)
327            dialogCreateOptions.optionFlags |= kNavNoTypePopup;
328
329#if TARGET_API_MAC_OSX
330        if (!(m_windowStyle & wxFD_OVERWRITE_PROMPT))
331            dialogCreateOptions.optionFlags |= kNavDontConfirmReplacement;
332#endif
333
334        err = ::NavCreatePutFileDialog(
335            &dialogCreateOptions,
336            kNavGenericSignature, // Suppresses the 'Default' (top) menu item
337            kNavGenericSignature,
338            sStandardNavEventFilter,
339            &myData, // for defaultLocation
340            &dialog );
341    }
342    else
343    {
344        // let the user select bundles/programs in dialogs
345        dialogCreateOptions.optionFlags |= kNavSupportPackages;
346
347        navFilterUPP = NewNavObjectFilterUPP(CrossPlatformFilterCallback);
348        err = ::NavCreateGetFileDialog(
349            &dialogCreateOptions,
350            NULL, // NavTypeListHandle
351            sStandardNavEventFilter,
352            NULL, // NavPreviewUPP
353            navFilterUPP,
354            (void *) &myData, // inClientData
355            &dialog );
356    }
357
358    if (err == noErr)
359        err = ::NavDialogRun(dialog);
360
361    // clean up filter related data, etc.
362    if (navFilterUPP)
363        ::DisposeNavObjectFilterUPP(navFilterUPP);
364
365    if (err != noErr)
366    {
367        ::NavDialogDispose(dialog);
368        return wxID_CANCEL;
369    }
370
371    NavReplyRecord navReply;
372    err = ::NavDialogGetReply(dialog, &navReply);
373    if (err == noErr && navReply.validRecord)
374    {
375        AEKeyword   theKeyword;
376        DescType    actualType;
377        Size        actualSize;
378        FSRef       theFSRef;
379        wxString thePath ;
380        long count;
381
382        m_filterIndex = myData.currentfilter;
383        ::AECountItems( &navReply.selection, &count );
384        for (long i = 1; i <= count; ++i)
385        {
386            err = ::AEGetNthPtr(
387                &(navReply.selection), i, typeFSRef, &theKeyword, &actualType,
388                &theFSRef, sizeof(theFSRef), &actualSize );
389            if (err != noErr)
390                break;
391
392            if (HasFdFlag(wxFD_SAVE))
393                thePath = wxMacFSRefToPath( &theFSRef, navReply.saveFileName );
394            else
395                thePath = wxMacFSRefToPath( &theFSRef );
396
397            if (!thePath)
398            {
399                ::NavDisposeReply(&navReply);
400                ::NavDialogDispose(dialog);
401                return wxID_CANCEL;
402            }
403
404            m_path = thePath;
405            m_paths.Add(m_path);
406            m_fileName = wxFileNameFromPath(m_path);
407            m_fileNames.Add(m_fileName);
408        }
409
410        // set these to the first hit
411        m_path = m_paths[0];
412        m_fileName = wxFileNameFromPath(m_path);
413        m_dir = wxPathOnly(m_path);
414    }
415
416    ::NavDisposeReply(&navReply);
417    ::NavDialogDispose(dialog);
418
419    return (err == noErr) ? wxID_OK : wxID_CANCEL;
420}
421
422#endif // wxUSE_FILEDLG
423
424