fileDialog.cpp

Engine/source/platformWin32/nativeDialogs/fileDialog.cpp

More...

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