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