fileDialog.cpp
Engine/source/platformWin32/nativeDialogs/fileDialog.cpp
Detailed Description
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "console/simBase.h" 25#include "platform/nativeDialogs/fileDialog.h" 26#include "platform/threads/mutex.h" 27#include "platformWin32/platformWin32.h" 28#include "core/util/safeDelete.h" 29#include "math/mMath.h" 30#include "core/strings/unicode.h" 31#include "console/consoleTypes.h" 32#include "platform/profiler.h" 33#include <ShlObj.h> 34#include <WindowsX.h> 35#include "console/engineAPI.h" 36 37#ifdef TORQUE_TOOLS 38//----------------------------------------------------------------------------- 39// PlatformFileDlgData Implementation 40//----------------------------------------------------------------------------- 41FileDialogData::FileDialogData() 42{ 43 // Default Path 44 // 45 // Try to provide consistent experience by recalling the last file path 46 // - else 47 // Default to Working Directory if last path is not set or is invalid 48 mDefaultPath = StringTable->insert( Con::getVariable("Tools::FileDialogs::LastFilePath") ); 49 if( mDefaultPath == StringTable->lookup("") || !Platform::isDirectory( mDefaultPath ) ) 50 mDefaultPath = Platform::getCurrentDirectory(); 51 52 mDefaultFile = StringTable->insert(""); 53 mFilters = StringTable->insert(""); 54 mFile = StringTable->insert(""); 55 mTitle = StringTable->insert(""); 56 57 mStyle = 0; 58 59} 60FileDialogData::~FileDialogData() 61{ 62 63} 64 65static LRESULT PASCAL OKBtnFolderHackProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 66{ 67 WNDPROC oldProc = (WNDPROC)GetProp(hWnd, dT("OldWndProc")); 68 69 switch(uMsg) 70 { 71 case WM_COMMAND: 72 if(LOWORD(wParam) == IDOK) 73 { 74 LPOPENFILENAME ofn = (LPOPENFILENAME)GetProp(hWnd, dT("OFN")); 75 if(ofn == NULL) 76 break; 77 78 SendMessage(hWnd, CDM_GETFILEPATH, ofn->nMaxFile, (LPARAM)ofn->lpstrFile); 79 80 char *filePath; 81#ifdef UNICODE 82 char fileBuf[MAX_PATH]; 83 convertUTF16toUTF8(ofn->lpstrFile, fileBuf); 84 filePath = fileBuf; 85#else 86 filePath = ofn->lpstrFile; 87#endif 88 89 if(Platform::isDirectory(filePath)) 90 { 91 // Got a directory 92 EndDialog(hWnd, IDOK); 93 } 94 } 95 break; 96 } 97 98 if(oldProc) 99 return CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); 100 else 101 return DefWindowProc(hWnd, uMsg, wParam, lParam); 102} 103 104static UINT_PTR CALLBACK FolderHookProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam){ 105 HWND hParent = GetParent(hdlg); 106 107 switch(uMsg) 108 { 109 case WM_INITDIALOG: 110 { 111 LPOPENFILENAME lpofn = (LPOPENFILENAME)lParam; 112 113 SendMessage(hParent, CDM_SETCONTROLTEXT, stc3, (LPARAM)dT("Folder name:")); 114 SendMessage(hParent, CDM_HIDECONTROL, cmb1, 0); 115 SendMessage(hParent, CDM_HIDECONTROL, stc2, 0); 116 117 LONG oldProc = SetWindowLong(hParent, GWLP_WNDPROC, (LONG)OKBtnFolderHackProc); 118 SetProp(hParent, dT("OldWndProc"), (HANDLE)oldProc); 119 SetProp(hParent, dT("OFN"), (HANDLE)lpofn); 120 } 121 break; 122 123 case WM_NOTIFY: 124 { 125 LPNMHDR nmhdr = (LPNMHDR)lParam; 126 switch(nmhdr->code) 127 { 128 case CDN_FOLDERCHANGE: 129 { 130 LPOFNOTIFY lpofn = (LPOFNOTIFY)lParam; 131 132 OpenFolderDialog *ofd = (OpenFolderDialog *)lpofn->lpOFN->lCustData; 133 134#ifdef UNICODE 135 UTF16 buf[MAX_PATH]; 136#else 137 char buf[MAX_PATH]; 138#endif 139 140 SendMessage(hParent, CDM_GETFOLDERPATH, sizeof(buf), (LPARAM)buf); 141 142 char filePath[MAX_PATH]; 143#ifdef UNICODE 144 convertUTF16toUTF8(buf, filePath); 145#else 146 dStrcpy( filePath, buf, MAX_PATH ); 147#endif 148 149 // [tom, 12/8/2006] Hack to remove files from the list because 150 // CDN_INCLUDEITEM doesn't work for regular files and folders. 151 HWND shellView = GetDlgItem(hParent, lst2); 152 HWND listView = FindWindowEx(shellView, 0, WC_LISTVIEW, NULL); 153 if(listView) 154 { 155 S32 count = ListView_GetItemCount(listView); 156 for(S32 i = count - 1;i >= 0;--i) 157 { 158 ListView_GetItemText(listView, i, 0, buf, sizeof(buf)); 159 160#ifdef UNICODE 161 char buf2[MAX_PATH]; 162 convertUTF16toUTF8(buf, buf2); 163#else 164 char *buf2 = buf; 165#endif 166 char full[MAX_PATH]; 167 dSprintf(full, sizeof(full), "%s\\%s", filePath, buf2); 168 169 if(!Platform::isDirectory(full)) 170 { 171 ListView_DeleteItem(listView, i); 172 } 173 } 174 } 175 176 if(ofd->mMustExistInDir == NULL || *ofd->mMustExistInDir == 0) 177 break; 178 179 HWND hOK = GetDlgItem(hParent, IDOK); 180 if(hOK == NULL) 181 break; 182 183 char checkPath[MAX_PATH]; 184 dSprintf(checkPath, sizeof(checkPath), "%s\\%s", filePath, ofd->mMustExistInDir); 185 186 EnableWindow(hOK, Platform::isFile(checkPath)); 187 } 188 break; 189 } 190 } 191 break; 192 } 193 return 0; 194} 195 196//----------------------------------------------------------------------------- 197// FileDialog Implementation 198//----------------------------------------------------------------------------- 199IMPLEMENT_CONOBJECT(FileDialog); 200 201ConsoleDocClass( FileDialog, 202 "@brief Base class responsible for displaying an OS file browser.\n\n" 203 204 "FileDialog is a platform agnostic dialog interface for querying the user for " 205 "file locations. It is designed to be used through the exposed scripting interface.\n\n" 206 207 "FileDialog is the base class for Native File Dialog controls in Torque. It provides these basic areas of functionality:\n\n" 208 " - Inherits from SimObject and is exposed to the scripting interface\n" 209 " - Provides blocking interface to allow instant return to script execution\n" 210 " - Simple object configuration makes practical use easy and effective\n\n" 211 212 "FileDialog is *NOT* intended to be used directly in script and is only exposed to script to expose generic file dialog attributes.\n\n" 213 214 "This base class is usable in TorqueScript, but is does not specify what functionality is intended (open or save?). " 215 "Its children, OpenFileDialog and SaveFileDialog, do make use of DialogStyle flags and do make use of specific funcationality. " 216 "These are the preferred classes to use\n\n" 217 218 "However, the FileDialog base class does contain the key properties and important method for file browing. The most " 219 "important function is Execute(). This is used by both SaveFileDialog and OpenFileDialog to initiate the browser.\n\n" 220 221 "@tsexample\n" 222 "// NOTE: This is not he preferred class to use, but this still works\n\n" 223 "// Create the file dialog\n" 224 "%baseFileDialog = new FileDialog()\n" 225 "{\n" 226 " // Allow browsing of all file types\n" 227 " filters = \"*.*\";\n\n" 228 " // No default file\n" 229 " defaultFile = "";\n\n" 230 " // Set default path relative to project\n" 231 " defaultPath = \"./\";\n\n" 232 " // Set the title\n" 233 " title = \"Durpa\";\n\n" 234 " // Allow changing of path you are browsing\n" 235 " changePath = true;\n" 236 "};\n\n" 237 " // Launch the file dialog\n" 238 " %baseFileDialog.Execute();\n" 239 " \n" 240 " // Don't forget to cleanup\n" 241 " %baseFileDialog.delete();\n\n\n" 242 "@endtsexample\n\n" 243 244 "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n" 245 246 "@see OpenFileDialog for a practical example on opening a file\n" 247 "@see SaveFileDialog for a practical example of saving a file\n" 248 249 "@ingroup FileSystem\n" 250); 251 252FileDialog::FileDialog() : mData() 253{ 254 // Default to File Must Exist Open Dialog style 255 mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST; 256 mChangePath = false; 257} 258 259FileDialog::~FileDialog() 260{ 261} 262 263void FileDialog::initPersistFields() 264{ 265 addProtectedField( "defaultPath", TypeString, Offset(mData.mDefaultPath, FileDialog), &setDefaultPath, &defaultProtectedGetFn, 266 "The default directory path when the dialog is shown." ); 267 268 addProtectedField( "defaultFile", TypeString, Offset(mData.mDefaultFile, FileDialog), &setDefaultFile, &defaultProtectedGetFn, 269 "The default file path when the dialog is shown." ); 270 271 addProtectedField( "fileName", TypeString, Offset(mData.mFile, FileDialog), &setFile, &defaultProtectedGetFn, 272 "The default file name when the dialog is shown." ); 273 274 addProtectedField( "filters", TypeString, Offset(mData.mFilters, FileDialog), &setFilters, &defaultProtectedGetFn, 275 "The filter string for limiting the types of files visible in the dialog. It makes use of the pipe symbol '|' " 276 "as a delimiter. For example:\n\n" 277 "'All Files|*.*'\n\n" 278 "'Image Files|*.png;*.jpg|Png Files|*.png|Jepg Files|*.jpg'" ); 279 280 addField( "title", TypeString, Offset(mData.mTitle, FileDialog), 281 "The title for the dialog." ); 282 283 addProtectedField( "changePath", TypeBool, Offset(mChangePath, FileDialog), &setChangePath, &getChangePath, 284 "True/False whether to set the working directory to the directory returned by the dialog." ); 285 286 Parent::initPersistFields(); 287} 288 289static const U32 convertUTF16toUTF8DoubleNULL( const UTF16 *unistring, UTF8 *outbuffer, U32 len) 290{ 291 AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator."); 292 PROFILE_START(convertUTF16toUTF8DoubleNULL); 293 U32 walked, nCodeunits, codeunitLen; 294 UTF32 middleman; 295 296 nCodeunits=0; 297 while( ! (*unistring == '\0' && *(unistring + 1) == '\0') && nCodeunits + 3 < len ) 298 { 299 walked = 1; 300 middleman = oneUTF16toUTF32(unistring,&walked); 301 codeunitLen = oneUTF32toUTF8(middleman, &outbuffer[nCodeunits]); 302 unistring += walked; 303 nCodeunits += codeunitLen; 304 } 305 306 nCodeunits = getMin(nCodeunits,len - 1); 307 outbuffer[nCodeunits] = '\0'; 308 outbuffer[nCodeunits+1] = '\0'; 309 310 PROFILE_END(); 311 return nCodeunits; 312} 313 314// 315// Execute Method 316// 317bool FileDialog::Execute() 318{ 319 static char pszResult[MAX_PATH]; 320#ifdef UNICODE 321 UTF16 pszFile[MAX_PATH]; 322 UTF16 pszInitialDir[MAX_PATH]; 323 UTF16 pszTitle[MAX_PATH]; 324 UTF16 pszFilter[1024]; 325 UTF16 pszFileTitle[MAX_PATH]; 326 UTF16 pszDefaultExtension[MAX_PATH]; 327 // Convert parameters to UTF16*'s 328 convertUTF8toUTF16((UTF8 *)mData.mDefaultFile, pszFile); 329 convertUTF8toUTF16((UTF8 *)mData.mDefaultPath, pszInitialDir); 330 convertUTF8toUTF16((UTF8 *)mData.mTitle, pszTitle); 331 convertUTF8toUTF16((UTF8 *)mData.mFilters, pszFilter); 332#else 333 // Not Unicode, All char*'s! 334 char pszFile[MAX_PATH]; 335 char pszFilter[1024]; 336 char pszFileTitle[MAX_PATH]; 337 dStrcpy( pszFile, mData.mDefaultFile, MAX_PATH ); 338 dStrcpy( pszFilter, mData.mFilters, 1024 ); 339 const char* pszInitialDir = mData.mDefaultPath; 340 const char* pszTitle = mData.mTitle; 341 342#endif 343 344 pszFileTitle[0] = 0; 345 346 // Convert Filters 347 U32 filterLen = dStrlen( pszFilter ); 348 S32 dotIndex = -1; 349 for( U32 i = 0; i < filterLen; i++ ) 350 { 351 if( pszFilter[i] == '|' ) 352 pszFilter[i] = '\0'; 353 354 if( pszFilter[ i ] == '.' && dotIndex == -1 ) 355 dotIndex = i; 356 } 357 // Add second NULL terminator at the end 358 pszFilter[ filterLen + 1 ] = '\0'; 359 360 // Get default extension. 361 dMemset( pszDefaultExtension, 0, sizeof( pszDefaultExtension ) ); 362 if( dotIndex != -1 ) 363 { 364 for( U32 i = 0; i < MAX_PATH; ++ i ) 365 { 366 UTF16 ch = pszFilter[ dotIndex + 1 + i ]; 367 if( !ch || ch == ';' || ch == '|' || dIsspace( ch ) ) 368 break; 369 370 pszDefaultExtension[ i ] = ch; 371 } 372 } 373 374 OPENFILENAME ofn; 375 dMemset(&ofn, 0, sizeof(ofn)); 376 ofn.lStructSize = sizeof(ofn); 377 ofn.hwndOwner = getWin32WindowHandle(); 378 ofn.lpstrFile = pszFile; 379 380 381 if( !dStrncmp( mData.mDefaultFile, "", 1 ) ) 382 ofn.lpstrFile[0] = '\0'; 383 384 385 ofn.nMaxFile = sizeof(pszFile); 386 ofn.lpstrFilter = pszFilter; 387 ofn.nFilterIndex = 1; 388 ofn.lpstrInitialDir = pszInitialDir; 389 ofn.lCustData = (LPARAM)this; 390 ofn.lpstrFileTitle = pszFileTitle; 391 ofn.nMaxFileTitle = sizeof(pszFileTitle); 392 ofn.lpstrDefExt = pszDefaultExtension[ 0 ] ? pszDefaultExtension : NULL; 393 394 if( mData.mTitle != StringTable->lookup("") ) 395 ofn.lpstrTitle = pszTitle; 396 397 // Build Proper Flags. 398 ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_HIDEREADONLY; 399 400 if(mData.mStyle & FileDialogData::FDS_BROWSEFOLDER) 401 { 402 ofn.lpfnHook = FolderHookProc; 403 ofn.Flags |= OFN_ENABLEHOOK; 404 } 405 406 if( !(mData.mStyle & FileDialogData::FDS_CHANGEPATH) ) 407 ofn.Flags |= OFN_NOCHANGEDIR; 408 409 if( mData.mStyle & FileDialogData::FDS_MUSTEXIST ) 410 ofn.Flags |= OFN_FILEMUSTEXIST; 411 412 if( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ) 413 ofn.Flags |= OFN_ALLOWMULTISELECT; 414 415 if( mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT ) 416 ofn.Flags |= OFN_OVERWRITEPROMPT; 417 418 419 // Flag we're showing file browser so we can do some render hacking 420 winState.renderThreadBlocked = true; 421 422 // Get the current working directory, so we can back up to it once Windows has 423 // done its craziness and messed with it. 424 StringTableEntry cwd = Platform::getCurrentDirectory(); 425 426 // Execute Dialog (Blocking Call) 427 bool dialogSuccess = false; 428 if( mData.mStyle & FileDialogData::FDS_OPEN ) 429 dialogSuccess = GetOpenFileName(&ofn); 430 else if( mData.mStyle & FileDialogData::FDS_SAVE ) 431 dialogSuccess = GetSaveFileName(&ofn); 432 433 // Dialog is gone. 434 winState.renderThreadBlocked = false; 435 436 // Restore the working directory. 437 Platform::setCurrentDirectory( cwd ); 438 439 // Did we select a file? 440 if( !dialogSuccess ) 441 return false; 442 443 // Handle Result Properly for Unicode as well as ANSI 444#ifdef UNICODE 445 if(pszFileTitle[0] || ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )) 446 convertUTF16toUTF8( (UTF16*)pszFile, pszResult); 447 else 448 convertUTF16toUTF8DoubleNULL( (UTF16*)pszFile, (UTF8*)pszResult, sizeof(pszResult)); 449#else 450 if(pszFileTitle[0] || ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )) 451 dStrcpy(pszResult,pszFile,MAX_PATH); 452 else 453 { 454 // [tom, 1/4/2007] pszResult is a double-NULL terminated, NULL separated list in this case so we can't just dSstrcpy() 455 char *sptr = pszFile, *dptr = pszResult; 456 while(! (*sptr == 0 && *(sptr+1) == 0)) 457 *dptr++ = *sptr++; 458 *dptr++ = 0; 459 } 460#endif 461 462 forwardslash(pszResult); 463 464 // [tom, 1/5/2007] Windows is ridiculously dumb. If you select a single file in a multiple 465 // select file dialog then it will return the file the same way as it would in a single 466 // select dialog. The only difference is pszFileTitle is empty if multiple files 467 // are selected. 468 469 // Store the result on our object 470 if( mData.mStyle & FileDialogData::FDS_BROWSEFOLDER || ( pszFileTitle[0] && ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ))) 471 { 472 // Single file selection, do it the easy way 473 mData.mFile = StringTable->insert( pszResult ); 474 } 475 else if(pszFileTitle[0] && ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )) 476 { 477 // Single file selection in a multiple file selection dialog 478 setDataField(StringTable->insert("files"), "0", pszResult); 479 setDataField(StringTable->insert("fileCount"), NULL, "1"); 480 } 481 else 482 { 483 // Multiple file selection, break out into an array 484 S32 numFiles = 0; 485 const char *dir = pszResult; 486 const char *file = dir + dStrlen(dir) + 1; 487 char buffer[1024]; 488 489 while(*file) 490 { 491 Platform::makeFullPathName(file, buffer, sizeof(buffer), dir); 492 setDataField(StringTable->insert("files"), Con::getIntArg(numFiles++), buffer); 493 494 file = file + dStrlen(file) + 1; 495 } 496 497 setDataField(StringTable->insert("fileCount"), NULL, Con::getIntArg(numFiles)); 498 } 499 500 // Return success. 501 return true; 502 503} 504DefineEngineMethod( FileDialog, Execute, bool, (),, 505 "@brief Launches the OS file browser\n\n" 506 507 "After an Execute() call, the chosen file name and path is available in one of two areas. " 508 "If only a single file selection is permitted, the results will be stored in the @a fileName " 509 "attribute.\n\n" 510 511 "If multiple file selection is permitted, the results will be stored in the " 512 "@a files array. The total number of files in the array will be stored in the " 513 "@a fileCount attribute.\n\n" 514 515 "@tsexample\n" 516 "// NOTE: This is not he preferred class to use, but this still works\n\n" 517 "// Create the file dialog\n" 518 "%baseFileDialog = new FileDialog()\n" 519 "{\n" 520 " // Allow browsing of all file types\n" 521 " filters = \"*.*\";\n\n" 522 " // No default file\n" 523 " defaultFile = "";\n\n" 524 " // Set default path relative to project\n" 525 " defaultPath = \"./\";\n\n" 526 " // Set the title\n" 527 " title = \"Durpa\";\n\n" 528 " // Allow changing of path you are browsing\n" 529 " changePath = true;\n" 530 "};\n\n" 531 " // Launch the file dialog\n" 532 " %baseFileDialog.Execute();\n" 533 " \n" 534 " // Don't forget to cleanup\n" 535 " %baseFileDialog.delete();\n\n\n" 536 537 " // A better alternative is to use the \n" 538 " // derived classes which are specific to file open and save\n\n" 539 " // Create a dialog dedicated to opening files\n" 540 " %openFileDlg = new OpenFileDialog()\n" 541 " {\n" 542 " // Look for jpg image files\n" 543 " // First part is the descriptor|second part is the extension\n" 544 " Filters = \"Jepg Files|*.jpg\";\n" 545 " // Allow browsing through other folders\n" 546 " ChangePath = true;\n\n" 547 " // Only allow opening of one file at a time\n" 548 " MultipleFiles = false;\n" 549 " };\n\n" 550 " // Launch the open file dialog\n" 551 " %result = %openFileDlg.Execute();\n\n" 552 " // Obtain the chosen file name and path\n" 553 " if ( %result )\n" 554 " {\n" 555 " %seletedFile = %openFileDlg.file;\n" 556 " }\n" 557 " else\n" 558 " {\n" 559 " %selectedFile = \"\";\n" 560 " }\n" 561 " // Cleanup\n" 562 " %openFileDlg.delete();\n\n\n" 563 564 " // Create a dialog dedicated to saving a file\n" 565 " %saveFileDlg = new SaveFileDialog()\n" 566 " {\n" 567 " // Only allow for saving of COLLADA files\n" 568 " Filters = \"COLLADA Files (*.dae)|*.dae|\";\n\n" 569 " // Default save path to where the WorldEditor last saved\n" 570 " DefaultPath = $pref::WorldEditor::LastPath;\n\n" 571 " // No default file specified\n" 572 " DefaultFile = \"\";\n\n" 573 " // Do not allow the user to change to a new directory\n" 574 " ChangePath = false;\n\n" 575 " // Prompt the user if they are going to overwrite an existing file\n" 576 " OverwritePrompt = true;\n" 577 " };\n\n" 578 " // Launch the save file dialog\n" 579 " %result = %saveFileDlg.Execute();\n\n" 580 " // Obtain the file name\n" 581 " %selectedFile = \"\";\n" 582 " if ( %result )\n" 583 " %selectedFile = %saveFileDlg.file;\n\n" 584 " // Cleanup\n" 585 " %saveFileDlg.delete();\n" 586 "@endtsexample\n\n" 587 588 "@return True if the file was selected was successfully found (opened) or declared (saved).") 589{ 590 return object->Execute(); 591} 592 593//----------------------------------------------------------------------------- 594// Dialog Filters 595//----------------------------------------------------------------------------- 596bool FileDialog::setFilters( void *object, const char *index, const char *data ) 597{ 598 // Will do validate on write at some point. 599 if( !data ) 600 return true; 601 602 return true; 603 604}; 605 606 607//----------------------------------------------------------------------------- 608// Default Path Property - String Validated on Write 609//----------------------------------------------------------------------------- 610bool FileDialog::setDefaultPath( void *object, const char *index, const char *data ) 611{ 612 613 if( !data || !dStrncmp( data, "", 1 ) ) 614 return true; 615 616 // Copy and Backslash the path (Windows dialogs are VERY picky about this format) 617 static char szPathValidate[512]; 618 dStrcpy( szPathValidate, data, 512 ); 619 620 Platform::makeFullPathName( data,szPathValidate, sizeof(szPathValidate)); 621 backslash( szPathValidate ); 622 623 // Remove any trailing \'s 624 S8 validateLen = dStrlen( szPathValidate ); 625 if( szPathValidate[ validateLen - 1 ] == '\\' ) 626 szPathValidate[ validateLen - 1 ] = '\0'; 627 628 // Now check 629 if( Platform::isDirectory( szPathValidate ) ) 630 { 631 // Finally, assign in proper format. 632 FileDialog *pDlg = static_cast<FileDialog*>( object ); 633 pDlg->mData.mDefaultPath = StringTable->insert( szPathValidate ); 634 } 635#ifdef TORQUE_DEBUG 636 else 637 Con::errorf(ConsoleLogEntry::GUI, "FileDialog - Invalid Default Path Specified!"); 638#endif 639 640 return false; 641 642}; 643 644//----------------------------------------------------------------------------- 645// Default File Property - String Validated on Write 646//----------------------------------------------------------------------------- 647bool FileDialog::setDefaultFile( void *object, const char *index, const char *data ) 648{ 649 if( !data || !dStrncmp( data, "", 1 ) ) 650 return true; 651 652 // Copy and Backslash the path (Windows dialogs are VERY picky about this format) 653 static char szPathValidate[512]; 654 Platform::makeFullPathName( data,szPathValidate, sizeof(szPathValidate) ); 655 backslash( szPathValidate ); 656 657 // Remove any trailing \'s 658 S8 validateLen = dStrlen( szPathValidate ); 659 if( szPathValidate[ validateLen - 1 ] == '\\' ) 660 szPathValidate[ validateLen - 1 ] = '\0'; 661 662 // Finally, assign in proper format. 663 FileDialog *pDlg = static_cast<FileDialog*>( object ); 664 pDlg->mData.mDefaultFile = StringTable->insert( szPathValidate ); 665 666 return false; 667}; 668 669//----------------------------------------------------------------------------- 670// ChangePath Property - Change working path on successful file selection 671//----------------------------------------------------------------------------- 672bool FileDialog::setChangePath( void *object, const char *index, const char *data ) 673{ 674 bool bMustExist = dAtob( data ); 675 676 FileDialog *pDlg = static_cast<FileDialog*>( object ); 677 678 if( bMustExist ) 679 pDlg->mData.mStyle |= FileDialogData::FDS_CHANGEPATH; 680 else 681 pDlg->mData.mStyle &= ~<a href="/coding/class/structfiledialogdata/">FileDialogData</a>::FDS_CHANGEPATH; 682 683 return true; 684}; 685 686const char* FileDialog::getChangePath(void* obj, const char* data) 687{ 688 FileDialog *pDlg = static_cast<FileDialog*>( obj ); 689 if( pDlg->mData.mStyle & FileDialogData::FDS_CHANGEPATH ) 690 return StringTable->insert("true"); 691 else 692 return StringTable->insert("false"); 693} 694 695bool FileDialog::setFile( void *object, const char *index, const char *data ) 696{ 697 return false; 698}; 699 700//----------------------------------------------------------------------------- 701// OpenFileDialog Implementation 702//----------------------------------------------------------------------------- 703 704ConsoleDocClass( OpenFileDialog, 705 "@brief Derived from FileDialog, this class is responsible for opening a file browser with the intention of opening a file.\n\n" 706 707 "The core usage of this dialog is to locate a file in the OS and return the path and name. This does not handle " 708 "the actual file parsing or data manipulation. That functionality is left up to the FileObject class.\n\n" 709 710 "@tsexample\n" 711 " // Create a dialog dedicated to opening files\n" 712 " %openFileDlg = new OpenFileDialog()\n" 713 " {\n" 714 " // Look for jpg image files\n" 715 " // First part is the descriptor|second part is the extension\n" 716 " Filters = \"Jepg Files|*.jpg\";\n" 717 " // Allow browsing through other folders\n" 718 " ChangePath = true;\n\n" 719 " // Only allow opening of one file at a time\n" 720 " MultipleFiles = false;\n" 721 " };\n\n" 722 " // Launch the open file dialog\n" 723 " %result = %openFileDlg.Execute();\n\n" 724 " // Obtain the chosen file name and path\n" 725 " if ( %result )\n" 726 " {\n" 727 " %seletedFile = %openFileDlg.file;\n" 728 " }\n" 729 " else\n" 730 " {\n" 731 " %selectedFile = \"\";\n" 732 " }\n\n" 733 " // Cleanup\n" 734 " %openFileDlg.delete();\n\n\n" 735 "@endtsexample\n\n" 736 737 "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n" 738 739 "@see FileDialog\n" 740 "@see SaveFileDialog\n" 741 "@see FileObject\n" 742 743 "@ingroup FileSystem\n" 744); 745OpenFileDialog::OpenFileDialog() 746{ 747 // Default File Must Exist 748 mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST; 749} 750 751OpenFileDialog::~OpenFileDialog() 752{ 753 mMustExist = true; 754 mMultipleFiles = false; 755} 756 757IMPLEMENT_CONOBJECT(OpenFileDialog); 758 759//----------------------------------------------------------------------------- 760// Console Properties 761//----------------------------------------------------------------------------- 762void OpenFileDialog::initPersistFields() 763{ 764 addProtectedField("MustExist", TypeBool, Offset(mMustExist, OpenFileDialog), &setMustExist, &getMustExist, "True/False whether the file returned must exist or not" ); 765 addProtectedField("MultipleFiles", TypeBool, Offset(mMultipleFiles, OpenFileDialog), &setMultipleFiles, &getMultipleFiles, "True/False whether multiple files may be selected and returned or not" ); 766 767 Parent::initPersistFields(); 768} 769 770//----------------------------------------------------------------------------- 771// File Must Exist - Boolean 772//----------------------------------------------------------------------------- 773bool OpenFileDialog::setMustExist( void *object, const char *index, const char *data ) 774{ 775 bool bMustExist = dAtob( data ); 776 777 OpenFileDialog *pDlg = static_cast<OpenFileDialog*>( object ); 778 779 if( bMustExist ) 780 pDlg->mData.mStyle |= FileDialogData::FDS_MUSTEXIST; 781 else 782 pDlg->mData.mStyle &= ~<a href="/coding/class/structfiledialogdata/">FileDialogData</a>::FDS_MUSTEXIST; 783 784 return true; 785}; 786 787const char* OpenFileDialog::getMustExist(void* obj, const char* data) 788{ 789 OpenFileDialog *pDlg = static_cast<OpenFileDialog*>( obj ); 790 if( pDlg->mData.mStyle & FileDialogData::FDS_MUSTEXIST ) 791 return StringTable->insert("true"); 792 else 793 return StringTable->insert("false"); 794} 795 796//----------------------------------------------------------------------------- 797// Can Select Multiple Files - Boolean 798//----------------------------------------------------------------------------- 799bool OpenFileDialog::setMultipleFiles( void *object, const char *index, const char *data ) 800{ 801 bool bMustExist = dAtob( data ); 802 803 OpenFileDialog *pDlg = static_cast<OpenFileDialog*>( object ); 804 805 if( bMustExist ) 806 pDlg->mData.mStyle |= FileDialogData::FDS_MULTIPLEFILES; 807 else 808 pDlg->mData.mStyle &= ~<a href="/coding/class/structfiledialogdata/">FileDialogData</a>::FDS_MULTIPLEFILES; 809 810 return true; 811}; 812 813const char* OpenFileDialog::getMultipleFiles(void* obj, const char* data) 814{ 815 OpenFileDialog *pDlg = static_cast<OpenFileDialog*>( obj ); 816 if( pDlg->mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ) 817 return StringTable->insert("true"); 818 else 819 return StringTable->insert("false"); 820} 821 822//----------------------------------------------------------------------------- 823// SaveFileDialog Implementation 824//----------------------------------------------------------------------------- 825ConsoleDocClass( SaveFileDialog, 826 "@brief Derived from FileDialog, this class is responsible for opening a file browser with the intention of saving a file.\n\n" 827 828 "The core usage of this dialog is to locate a file in the OS and return the path and name. This does not handle " 829 "the actual file writing or data manipulation. That functionality is left up to the FileObject class.\n\n" 830 831 "@tsexample\n" 832 " // Create a dialog dedicated to opening file\n" 833 " %saveFileDlg = new SaveFileDialog()\n" 834 " {\n" 835 " // Only allow for saving of COLLADA files\n" 836 " Filters = \"COLLADA Files (*.dae)|*.dae|\";\n\n" 837 " // Default save path to where the WorldEditor last saved\n" 838 " DefaultPath = $pref::WorldEditor::LastPath;\n\n" 839 " // No default file specified\n" 840 " DefaultFile = \"\";\n\n" 841 " // Do not allow the user to change to a new directory\n" 842 " ChangePath = false;\n\n" 843 " // Prompt the user if they are going to overwrite an existing file\n" 844 " OverwritePrompt = true;\n" 845 " };\n\n" 846 " // Launch the save file dialog\n" 847 " %saveFileDlg.Execute();\n\n" 848 " if ( %result )\n" 849 " {\n" 850 " %seletedFile = %openFileDlg.file;\n" 851 " }\n" 852 " else\n" 853 " {\n" 854 " %selectedFile = \"\";\n" 855 " }\n\n" 856 " // Cleanup\n" 857 " %saveFileDlg.delete();\n" 858 "@endtsexample\n\n" 859 860 "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n" 861 862 "@see FileDialog\n" 863 "@see OpenFileDialog\n" 864 "@see FileObject\n" 865 866 "@ingroup FileSystem\n" 867); 868SaveFileDialog::SaveFileDialog() 869{ 870 // Default File Must Exist 871 mData.mStyle = FileDialogData::FDS_SAVE | FileDialogData::FDS_OVERWRITEPROMPT; 872 mOverwritePrompt = true; 873} 874 875SaveFileDialog::~SaveFileDialog() 876{ 877} 878 879IMPLEMENT_CONOBJECT(SaveFileDialog); 880 881//----------------------------------------------------------------------------- 882// Console Properties 883//----------------------------------------------------------------------------- 884void SaveFileDialog::initPersistFields() 885{ 886 addProtectedField("OverwritePrompt", TypeBool, Offset(mOverwritePrompt, SaveFileDialog), &setOverwritePrompt, &getOverwritePrompt, "True/False whether the dialog should prompt before accepting an existing file name" ); 887 888 Parent::initPersistFields(); 889} 890 891//----------------------------------------------------------------------------- 892// Prompt on Overwrite - Boolean 893//----------------------------------------------------------------------------- 894bool SaveFileDialog::setOverwritePrompt( void *object, const char *index, const char *data ) 895{ 896 bool bMustExist = dAtob( data ); 897 898 SaveFileDialog *pDlg = static_cast<SaveFileDialog*>( object ); 899 900 if( bMustExist ) 901 pDlg->mData.mStyle |= FileDialogData::FDS_OVERWRITEPROMPT; 902 else 903 pDlg->mData.mStyle &= ~<a href="/coding/class/structfiledialogdata/">FileDialogData</a>::FDS_OVERWRITEPROMPT; 904 905 return true; 906}; 907 908const char* SaveFileDialog::getOverwritePrompt(void* obj, const char* data) 909{ 910 SaveFileDialog *pDlg = static_cast<SaveFileDialog*>( obj ); 911 if( pDlg->mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT ) 912 return StringTable->insert("true"); 913 else 914 return StringTable->insert("false"); 915} 916 917//----------------------------------------------------------------------------- 918// OpenFolderDialog Implementation 919//----------------------------------------------------------------------------- 920 921OpenFolderDialog::OpenFolderDialog() 922{ 923 mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_OVERWRITEPROMPT | FileDialogData::FDS_BROWSEFOLDER; 924 925 mMustExistInDir = ""; 926} 927 928IMPLEMENT_CONOBJECT(OpenFolderDialog); 929 930ConsoleDocClass( OpenFolderDialog, 931 "@brief OS level dialog used for browsing folder structures.\n\n" 932 933 "This is essentially an OpenFileDialog, but only used for returning directory paths, not files.\n\n" 934 935 "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n" 936 937 "@see OpenFileDialog for more details on functionality.\n\n" 938 939 "@ingroup FileSystem\n" 940); 941 942void OpenFolderDialog::initPersistFields() 943{ 944 addField("fileMustExist", TypeFilename, Offset(mMustExistInDir, OpenFolderDialog), "File that must be in selected folder for it to be valid"); 945 946 Parent::initPersistFields(); 947} 948 949#endif 950