1///
2// Copied from wxWidgets 3.0.2 and modified to support additional features
3//
4/////////////////////////////////////////////////////////////////////////////
5// Name:        src/cocoa/filedlg.mm
6// Purpose:     wxFileDialog for wxCocoa
7// Author:      Ryan Norton
8// Modified by: Leland Lucius
9// Created:     2004-10-02
10// Copyright:   (c) Ryan Norton
11// Licence:     wxWindows licence
12/////////////////////////////////////////////////////////////////////////////
13
14// ============================================================================
15// declarations
16// ============================================================================
17
18// ----------------------------------------------------------------------------
19// headers
20// ----------------------------------------------------------------------------
21
22#include "Internat.h"
23#include "../FileDialog.h"
24
25#include <wx/app.h>
26#include <wx/choice.h>
27#include <wx/clipbrd.h>
28#include <wx/evtloop.h>
29#include <wx/filectrl.h>
30#include <wx/filename.h>
31#include <wx/modalhook.h>
32#include <wx/sizer.h>
33#include <wx/sysopt.h>
34#include <wx/stattext.h>
35#include <wx/tokenzr.h>
36
37#include <wx/osx/core/private.h>
38
39#include <AppKit/AppKit.h>
40
41// ============================================================================
42// implementation
43// ============================================================================
44
45@interface OSPanelDelegate : NSObject <NSOpenSavePanelDelegate>
46{
47    FileDialog* _dialog;
48}
49
50- (FileDialog*) fileDialog;
51- (void) setFileDialog:(FileDialog*) dialog;
52
53- (void)panel:(id)sender didChangeToDirectoryURL:(NSURL *)url;
54- (void)panelSelectionDidChange:(id)sender;
55- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError * _Nullable *)outError;
56
57- (void)viewResized:(NSNotification *)notification;
58
59@end
60
61@implementation OSPanelDelegate
62- (void)viewResized:(NSNotification *)notification
63{
64   _dialog->DoViewResized([notification object]);
65}
66
67- (id) init
68{
69    if ( self = [super init] )
70    {
71        _dialog = NULL;
72    }
73    return self;
74}
75
76- (FileDialog*) fileDialog
77{
78    return _dialog;
79}
80
81- (void) setFileDialog:(FileDialog*) dialog
82{
83    _dialog = dialog;
84}
85
86- (void)panel:(id)sender didChangeToDirectoryURL:(NSURL *)url AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER
87{
88    wxString path = wxCFStringRef::AsStringWithNormalizationFormC( [url path] );
89
90    _dialog->DoSendFolderChangedEvent(sender, path);
91}
92
93- (void)panelSelectionDidChange:(id)sender AVAILABLE_MAC_OS_X_VERSION_10_3_AND_LATER
94{
95    _dialog->DoSendSelectionChangedEvent(sender);
96}
97
98// Do NOT remove this method.  For an explanation, refer to:
99//
100//    http://bugzilla.audacityteam.org/show_bug.cgi?id=2371
101//
102- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError * _Nullable *)outError;
103{
104   // We handle filename validation after the panel closes
105   return YES;
106}
107
108@end
109
110wxIMPLEMENT_CLASS(FileDialog, FileDialogBase)
111
112FileDialog::FileDialog()
113:   FileDialogBase()
114{
115    Init();
116}
117
118FileDialog::FileDialog(wxWindow *parent,
119                       const wxString& message,
120                       const wxString& defaultDir,
121                       const wxString& defaultFile,
122                       const wxString& wildCard,
123                       long style,
124                       const wxPoint& pos,
125                       const wxSize& sz,
126                       const wxString& name)
127:   FileDialogBase()
128{
129    Init();
130
131    Create(parent,message,defaultDir,defaultFile,wildCard,style,pos,sz,name);
132}
133
134void FileDialog::Init()
135{
136    m_filterIndex = -1;
137    m_delegate = nil;
138    m_filterPanel = NULL;
139    m_filterChoice = NULL;
140}
141
142void FileDialog::Create(
143    wxWindow *parent, const wxString& message,
144    const wxString& defaultDir, const wxString& defaultFileName, const wxString& wildCard,
145    long style, const wxPoint& pos, const wxSize& sz, const wxString& name)
146{
147
148    FileDialogBase::Create(parent, message, defaultDir, defaultFileName, wildCard, style, pos, sz, name);
149}
150
151FileDialog::~FileDialog()
152{
153}
154
155bool FileDialog::SupportsExtraControl() const
156{
157    return true;
158}
159
160NSArray* GetTypesFromExtension( const wxString extensiongroup, wxArrayString& extensions )
161{
162    NSMutableArray* types = nil;
163    extensions.Clear();
164
165    wxStringTokenizer tokenizer( extensiongroup, wxT(";") ) ;
166    while ( tokenizer.HasMoreTokens() )
167    {
168        wxString extension = tokenizer.GetNextToken() ;
169        // Remove leading '*'
170        if ( extension.length() && (extension.GetChar(0) == '*') )
171            extension = extension.Mid( 1 );
172
173        // Remove leading '.'
174        if ( extension.length() && (extension.GetChar(0) == '.') )
175            extension = extension.Mid( 1 );
176
177        // Remove leading '*', this is for handling *.*
178        if ( extension.length() && (extension.GetChar(0) == '*') )
179            extension = extension.Mid( 1 );
180
181        if ( extension.IsEmpty() )
182        {
183            extensions.Clear();
184            [types release];
185            types = nil;
186            return nil;
187        }
188
189        if ( types == nil )
190            types = [[NSMutableArray alloc] init];
191
192        extensions.Add(extension.Lower());
193        wxCFStringRef cfext(extension);
194        [types addObject: (NSString*)cfext.AsNSString()  ];
195    }
196
197    [types autorelease];
198    return types;
199}
200
201NSArray* GetTypesFromFilter( const wxString& filter, wxArrayString& names, wxArrayString& extensiongroups )
202{
203    NSMutableArray* types = nil;
204    bool allowAll = false;
205
206    names.Clear();
207    extensiongroups.Clear();
208
209    if ( !filter.empty() )
210    {
211        wxStringTokenizer tokenizer( filter, wxT("|") );
212        int numtokens = (int)tokenizer.CountTokens();
213        if(numtokens == 1)
214        {
215            // we allow for compatibility reason to have a single filter expression (like *.*) without
216            // an explanatory text, in that case the first part is name and extension at the same time
217            wxString extension = tokenizer.GetNextToken();
218            names.Add( extension );
219            extensiongroups.Add( extension );
220        }
221        else
222        {
223            int numextensions = numtokens / 2;
224            for(int i = 0; i < numextensions; i++)
225            {
226                wxString name = tokenizer.GetNextToken();
227                wxString extension = tokenizer.GetNextToken();
228                names.Add( name );
229                extensiongroups.Add( extension );
230            }
231        }
232
233        const size_t extCount = extensiongroups.GetCount();
234        wxArrayString extensions;
235        for ( size_t i = 0 ; i < extCount; i++ )
236        {
237            NSArray* exttypes = GetTypesFromExtension(extensiongroups[i], extensions);
238            if ( exttypes != nil )
239            {
240                if ( allowAll == false )
241                {
242                    if ( types == nil )
243                        types = [[NSMutableArray alloc] init];
244
245                    [types addObjectsFromArray:exttypes];
246                }
247            }
248            else
249            {
250                allowAll = true;
251                [types release];
252                types = nil;
253            }
254        }
255    }
256    [types autorelease];
257    return types;
258}
259
260void FileDialog::DoOnFilterSelected(int index)
261{
262    if (index == wxNOT_FOUND)
263    {
264      return;
265    }
266
267    NSArray* types = GetTypesFromExtension(m_filterExtensions[index],m_currentExtensions);
268    NSSavePanel* panel = (NSSavePanel*) GetWXWindow();
269    [panel setAllowedFileTypes:types];
270
271    m_filterIndex = index;
272
273    wxFileCtrlEvent event( wxEVT_FILECTRL_FILTERCHANGED, this, GetId() );
274    event.SetFilterIndex( m_filterIndex );
275    GetEventHandler()->ProcessEvent( event );
276}
277
278// An item has been selected in the file filter wxChoice:
279void FileDialog::OnFilterSelected( wxCommandEvent &WXUNUSED(event) )
280{
281    DoOnFilterSelected( m_filterChoice->GetSelection() );
282}
283
284void FileDialog::DoViewResized(void* object)
285{
286   m_filterPanel->Layout();
287}
288
289void FileDialog::DoSendFolderChangedEvent(void* panel, const wxString & path)
290{
291    m_dir = path;
292
293    wxFileCtrlEvent event( wxEVT_FILECTRL_FOLDERCHANGED, this, GetId() );
294
295    event.SetDirectory( m_dir );
296
297    GetEventHandler()->ProcessEvent( event );
298}
299
300void FileDialog::DoSendSelectionChangedEvent(void* panel)
301{
302    if ( HasFlag( wxFD_SAVE ) )
303    {
304        NSSavePanel* sPanel = (NSSavePanel*) panel;
305        NSString* path = [[sPanel URL] path];
306        wxFileName fn(wxCFStringRef::AsStringWithNormalizationFormC( path ));
307        if (!fn.GetFullPath().empty())
308        {
309            m_path = fn.GetFullPath();
310            m_dir = fn.GetPath();
311            m_fileName = fn.GetFullName();
312            m_fileNames.Clear();
313            m_fileNames.Add( m_fileName );
314        }
315    }
316    else
317    {
318        NSOpenPanel* oPanel = (NSOpenPanel*) panel;
319        m_paths.Clear();
320        m_fileNames.Clear();
321
322        NSArray* urls = [oPanel URLs];
323        for ( size_t i = 0 ; i < [urls count] ; ++ i )
324        {
325            NSString *path = [[urls objectAtIndex:i] path];
326            wxString fnstr = wxCFStringRef::AsStringWithNormalizationFormC( path );
327            m_paths.Add( fnstr );
328            m_fileNames.Add( wxFileNameFromPath( fnstr ) );
329            if ( i == 0 )
330            {
331                m_path = fnstr;
332                m_fileName = wxFileNameFromPath( fnstr );
333                m_dir = wxPathOnly( fnstr );
334            }
335        }
336    }
337
338    wxFileCtrlEvent event( wxEVT_FILECTRL_SELECTIONCHANGED, this, GetId() );
339
340    event.SetDirectory( m_dir );
341    event.SetFiles( m_fileNames );
342
343    GetEventHandler()->ProcessEvent( event );
344}
345
346void FileDialog::SetupExtraControls(WXWindow nativeWindow)
347{
348    NSSavePanel* panel = (NSSavePanel*) nativeWindow;
349    // for sandboxed app we cannot access the outer structures
350    // this leads to problems with extra controls, so as a temporary
351    // workaround for crashes we don't support those yet
352    if ( [panel contentView] == nil || getenv("APP_SANDBOX_CONTAINER_ID") != NULL )
353        return;
354
355    OSPanelDelegate* del = [[OSPanelDelegate alloc]init];
356    [del setFileDialog:this];
357    [panel setDelegate:del];
358    m_delegate = del;
359
360    wxNonOwnedWindow::Create( GetParent(), nativeWindow );
361
362    m_filterPanel = NULL;
363    m_filterChoice = NULL;
364    NSView* accView = nil;
365
366    if ( m_useFileTypeFilter || HasUserPaneCreator() )
367    {
368        wxBoxSizer *verticalSizer = new wxBoxSizer( wxVERTICAL );
369
370        // FINALLY FOUND IT! Creating the panel with "this" as the parent causes
371        // an exception and stack trace to be printed to stderr:
372        //
373        //   2021-02-17 13:52:14.550 Audacity[69217:891282] warning: <NSRemoteView: 0x7f92f4e67410 com.apple.appkit.xpc.openAndSavePanelService ((null)) NSSavePanelService> ignoring attempt to mutate its subviews (
374        //      0   ViewBridge                          0x00007fff6596685d -[NSRemoteView _announceSubviewMutationDisallowed] + 29
375        //      1   libwx_osx_cocoau_debug_core-3.1.3.0 0x0000000111c3abf1 _ZN17wxWidgetCocoaImpl5EmbedEP12wxWidgetImpl + 177
376        //
377        // It's because wxPanel tries to embed the wxPanel into the NSSavePanel and
378        // that's not allowed. Everything still works fine, so it can be ignored.
379        // But, if you want to dig into it further, changing the "this" parent to
380        // GetParent() gets rid of the exception. However, events from the extra
381        // controls in the accessory view do not get handled correctly.
382
383        m_filterPanel = new wxPanel( this, wxID_ANY );
384        accView = m_filterPanel->GetHandle();
385
386        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
387        [center addObserver:del
388                   selector:@selector(viewResized:)
389                       name:NSViewFrameDidChangeNotification
390                     object:accView];
391
392        if ( m_useFileTypeFilter )
393        {
394            wxBoxSizer *horizontalSizer = new wxBoxSizer( wxHORIZONTAL );
395
396            wxStaticText *stattext = new wxStaticText( m_filterPanel, wxID_ANY, XO("File type:") .Translation());
397            horizontalSizer->Add( stattext, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
398
399            m_filterChoice = new wxChoice( m_filterPanel, wxID_ANY );
400            m_filterChoice->Append( m_filterNames );
401            if ( m_filterNames.GetCount() > 0 )
402            {
403                if ( m_firstFileTypeFilter >= 0 )
404                    m_filterChoice->SetSelection( m_firstFileTypeFilter );
405            }
406            m_filterChoice->Bind(wxEVT_CHOICE, &FileDialog::OnFilterSelected, this);
407
408            horizontalSizer->Add( m_filterChoice, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
409            verticalSizer->Add( horizontalSizer, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
410        }
411
412        if ( HasUserPaneCreator() )
413        {
414            wxPanel *userpane = new wxPanel( m_filterPanel, wxID_ANY );
415            CreateUserPane( userpane );
416
417            wxBoxSizer *horizontalSizer = new wxBoxSizer( wxHORIZONTAL );
418            horizontalSizer->Add( userpane, 1, wxEXPAND, 0 );
419            verticalSizer->Add( horizontalSizer, 1, wxEXPAND, 0 );
420        }
421
422        m_filterPanel->SetSizer( verticalSizer );
423        m_filterPanel->Layout();
424
425        wxSize ws = m_filterPanel->GetBestSize();
426        m_filterPanel->SetSize(ws);
427        m_filterPanel->SetMinSize(ws);
428    }
429
430    if ( accView != nil )
431    {
432        [accView removeFromSuperview];
433        [accView setAutoresizingMask:NSViewWidthSizable];
434
435        [panel setAccessoryView:accView];
436    }
437}
438
439int FileDialog::ShowModal()
440{
441    WX_HOOK_MODAL_DIALOG();
442
443    wxCFEventLoopPauseIdleEvents pause;
444
445    wxMacAutoreleasePool autoreleasepool;
446
447    wxCFStringRef cf( m_message );
448
449    wxCFStringRef dir( m_dir );
450    wxCFStringRef file( m_fileName );
451
452    m_path.clear();
453    m_fileNames.Clear();
454    m_paths.Clear();
455
456    wxNonOwnedWindow* parentWindow = NULL;
457    int returnCode = -1;
458
459    if (GetParent())
460    {
461        parentWindow = dynamic_cast<wxNonOwnedWindow*>(wxGetTopLevelParent(GetParent()));
462    }
463
464    NSArray* types = GetTypesFromFilter( m_wildCard, m_filterNames, m_filterExtensions ) ;
465
466    m_useFileTypeFilter = m_filterExtensions.GetCount() > 0;
467
468#if defined(we_always_want_the_types)
469    if( HasFlag(wxFD_OPEN) )
470    {
471        if ( !(wxSystemOptions::HasOption( wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES ) && (wxSystemOptions::GetOptionInt( wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES ) == 1)) )
472            m_useFileTypeFilter = false;
473    }
474#endif
475
476    m_firstFileTypeFilter = wxNOT_FOUND;
477
478    if ( m_useFileTypeFilter
479        && m_filterIndex >= 0 && m_filterIndex < m_filterExtensions.GetCount() )
480    {
481        m_firstFileTypeFilter = m_filterIndex;
482    }
483    else if ( m_useFileTypeFilter )
484    {
485        types = nil;
486        bool useDefault = true;
487        for ( size_t i = 0; i < m_filterExtensions.GetCount(); ++i )
488        {
489            types = GetTypesFromExtension(m_filterExtensions[i], m_currentExtensions);
490            if ( m_currentExtensions.GetCount() == 0 )
491            {
492                useDefault = false;
493                m_firstFileTypeFilter = i;
494                break;
495            }
496
497            for ( size_t j = 0; j < m_currentExtensions.GetCount(); ++j )
498            {
499                if ( m_fileName.EndsWith(m_currentExtensions[j]) )
500                {
501                    m_firstFileTypeFilter = i;
502                    useDefault = false;
503                    break;
504                }
505            }
506            if ( !useDefault )
507                break;
508        }
509        if ( useDefault )
510        {
511            types = GetTypesFromExtension(m_filterExtensions[0], m_currentExtensions);
512            m_firstFileTypeFilter = 0;
513        }
514    }
515
516    OSXBeginModalDialog();
517
518    if ( HasFlag(wxFD_SAVE) )
519    {
520        NSSavePanel* sPanel = [NSSavePanel savePanel];
521
522        SetupExtraControls(sPanel);
523
524        // PRL:
525        // Hack for bugs 1300/1579: Intercept key down events, implementing
526        // copy/cut/paste, by invoking appropriate selectors. This is done
527        // because we do not use the wxWidgets IDs for the menu equivalents.
528        id handler;
529        if (wxTheClipboard->IsSupported(wxDF_UNICODETEXT)) {
530           handler = [
531              NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask
532              handler:^NSEvent *(NSEvent *event)
533              {
534                 auto app = [NSApplication sharedApplication];
535                 if ([event modifierFlags] & NSCommandKeyMask)
536                 {
537                    auto chars = [event charactersIgnoringModifiers];
538                    if ([chars isEqualToString:@"a"])
539                    {
540                          [app sendAction:@selector(selectAll:) to:nil from:nil];
541                    }
542                    else if ([chars isEqualToString:@"c"])
543                    {
544                          [app sendAction:@selector(copy:) to:nil from:nil];
545                    }
546                    else if ([chars isEqualToString:@"x"])
547                    {
548                          [app sendAction:@selector(cut:) to:nil from:nil];
549                    }
550                    else if ([chars isEqualToString:@"v"])
551                    {
552                          [app sendAction:@selector(paste:) to:nil from:nil];
553                    }
554                 }
555                 return event;
556              }
557           ];
558        }
559
560        // makes things more convenient:
561        [sPanel setCanCreateDirectories:YES];
562        [sPanel setMessage:cf.AsNSString()];
563        // if we should be able to descend into packages we must somehow
564        // be able to pass this in
565        [sPanel setTreatsFilePackagesAsDirectories:NO];
566        [sPanel setCanSelectHiddenExtension:YES];
567        [sPanel setExtensionHidden:NO];
568        [sPanel setAllowedFileTypes:types];
569        [sPanel setAllowsOtherFileTypes:YES];
570
571        if ( HasFlag(wxFD_OVERWRITE_PROMPT) )
572        {
573        }
574
575        /*
576        Let the file dialog know what file type should be used initially.
577        If this is not done then when setting the filter index
578        programmatically to 1 the file will still have the extension
579        of the first file type instead of the second one. E.g. when file
580        types are foo and bar, a filename "myletter" with SetDialogIndex(1)
581        would result in saving as myletter.foo, while we want myletter.bar.
582        */
583//        if(m_firstFileTypeFilter > 0)
584        {
585            DoOnFilterSelected(m_firstFileTypeFilter);
586        }
587
588        [sPanel setDirectoryURL:[NSURL fileURLWithPath:dir.AsNSString()]];
589        [sPanel setNameFieldStringValue:file.AsNSString()];
590        returnCode = [sPanel runModal];
591        ModalFinishedCallback(sPanel, returnCode);
592        if (wxTheClipboard->IsSupported(wxDF_UNICODETEXT))
593           [NSEvent removeMonitor:handler];
594    }
595    else
596    {
597        NSOpenPanel* oPanel = [NSOpenPanel openPanel];
598
599        SetupExtraControls(oPanel);
600
601        [oPanel setTreatsFilePackagesAsDirectories:NO];
602        [oPanel setCanChooseDirectories:NO];
603        [oPanel setResolvesAliases:YES];
604        [oPanel setCanChooseFiles:YES];
605        [oPanel setMessage:cf.AsNSString()];
606        [oPanel setAllowsMultipleSelection: (HasFlag(wxFD_MULTIPLE) ? YES : NO )];
607
608        // Note that the test here is intentionally different from the one
609        // above, in the wxFD_SAVE case: we need to call DoOnFilterSelected()
610        // even for m_firstFileTypeFilter == 0, i.e. when using the default
611        // filter.
612        if ( m_firstFileTypeFilter >= 0 )
613        {
614            DoOnFilterSelected(m_firstFileTypeFilter);
615        }
616        else
617        {
618            [oPanel setAllowedFileTypes: (m_delegate == nil ? types : nil)];
619        }
620        if ( !m_dir.IsEmpty() )
621            [oPanel setDirectoryURL:[NSURL fileURLWithPath:dir.AsNSString()
622                                               isDirectory:YES]];
623
624        {
625            DoOnFilterSelected(m_firstFileTypeFilter);
626        }
627
628        returnCode = [oPanel runModal];
629
630        ModalFinishedCallback(oPanel, returnCode);
631    }
632
633    OSXEndModalDialog();
634
635    return GetReturnCode();
636}
637
638void FileDialog::ModalFinishedCallback(void* panel, int returnCode)
639{
640    m_paths.Clear();
641    m_fileNames.Clear();
642
643    int result = wxID_CANCEL;
644    if (HasFlag(wxFD_SAVE))
645    {
646        NSSavePanel* sPanel = (NSSavePanel*)panel;
647        if (returnCode == NSOKButton )
648        {
649            result = wxID_OK;
650
651            NSString* path = [[sPanel URL] path];
652            wxFileName fn(wxCFStringRef::AsStringWithNormalizationFormC( path ));
653            m_dir = fn.GetPath();
654            m_fileName = fn.GetFullName();
655            m_path = fn.GetFullPath();
656
657            if (m_filterChoice)
658            {
659                m_filterIndex = m_filterChoice->GetSelection();
660            }
661        }
662        [sPanel setDelegate:nil];
663    }
664    else
665    {
666        NSOpenPanel* oPanel = (NSOpenPanel*)panel;
667        if (returnCode == NSOKButton )
668        {
669            panel = oPanel;
670            result = wxID_OK;
671
672            if (m_filterChoice)
673            {
674                m_filterIndex = m_filterChoice->GetSelection();
675            }
676
677            NSArray* filenames = [oPanel URLs];
678            for ( size_t i = 0 ; i < [filenames count] ; ++ i )
679            {
680                wxString fnstr = wxCFStringRef::AsStringWithNormalizationFormC([[filenames objectAtIndex:i] path]);
681                m_paths.Add( fnstr );
682                m_fileNames.Add( wxFileNameFromPath(fnstr) );
683                if ( i == 0 )
684                {
685                    m_path = fnstr;
686                    m_fileName = wxFileNameFromPath(fnstr);
687                    m_dir = wxPathOnly( fnstr );
688                }
689            }
690        }
691        [oPanel setDelegate:nil];
692    }
693
694    if ( m_delegate )
695    {
696        [[NSNotificationCenter defaultCenter] removeObserver:m_delegate];
697
698        [m_delegate release];
699        m_delegate = nil;
700    }
701
702    SetReturnCode(result);
703
704    if (GetModality() == wxDIALOG_MODALITY_WINDOW_MODAL)
705        SendWindowModalDialogEvent ( wxEVT_WINDOW_MODAL_DIALOG_CLOSED  );
706
707    // workaround for sandboxed app, see above
708    if ( m_isNativeWindowWrapper )
709        UnsubclassWin();
710    [(NSSavePanel*) panel setAccessoryView:nil];
711}
712
713// Change the currently displayed extension
714void FileDialog::SetFileExtension(const wxString& extension)
715{
716    NSSavePanel* sPanel = (NSSavePanel*) GetWXWindow();
717    m_filterExtensions[m_filterIndex] = extension;
718    NSArray* types = GetTypesFromExtension(m_filterExtensions[m_filterIndex],m_currentExtensions);
719    [sPanel setAllowedFileTypes:types];
720}
721
722