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