fileDialog.cpp 32 KB

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