win32FileDialog.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 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 "sim/simBase.h"
  23. #include "platform/nativeDialogs/fileDialog.h"
  24. #include "platform/threads/mutex.h"
  25. #include "platformWin32/platformWin32.h"
  26. #include "memory/safeDelete.h"
  27. #include "math/mMath.h"
  28. #include "string/unicode.h"
  29. #include "console/consoleTypes.h"
  30. #include "debug/profiler.h"
  31. #include "io/resource/resourceManager.h"
  32. #include <ShlObj.h>
  33. #include <WindowsX.h>
  34. // [neo, 5/15/2007 - #2994]
  35. // Added code to resolve short cuts so we can allow short cuts to directories
  36. // in the folder browse dialog as well as normal directories.
  37. #include "win32DirectoryResolver.h"
  38. //-----------------------------------------------------------------------------
  39. // Support Functions
  40. //-----------------------------------------------------------------------------
  41. static void forwardslash(char *str)
  42. {
  43. while(*str)
  44. {
  45. if(*str == '\\')
  46. *str = '/';
  47. str++;
  48. }
  49. }
  50. static void backslash(char *str)
  51. {
  52. while(*str)
  53. {
  54. if(*str == '/')
  55. *str = '\\';
  56. str++;
  57. }
  58. }
  59. static LRESULT PASCAL OKBtnFolderHackProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  60. {
  61. WNDPROC oldProc = (WNDPROC)GetProp(hWnd, dT("OldWndProc"));
  62. switch(uMsg)
  63. {
  64. case WM_COMMAND:
  65. if(LOWORD(wParam) == IDOK)
  66. {
  67. LPOPENFILENAME ofn = (LPOPENFILENAME)GetProp(hWnd, dT("OFN"));
  68. if(ofn == NULL)
  69. break;
  70. SendMessage(hWnd, CDM_GETFILEPATH, ofn->nMaxFile, (LPARAM)ofn->lpstrFile);
  71. char *filePath;
  72. #ifdef UNICODE
  73. char fileBuf[MAX_PATH];
  74. convertUTF16toUTF8(ofn->lpstrFile, fileBuf, sizeof(fileBuf));
  75. filePath = fileBuf;
  76. #else
  77. filePath = ofn->lpstrFile;
  78. #endif
  79. if(Platform::isDirectory(filePath))
  80. {
  81. // Got a directory
  82. EndDialog(hWnd, IDOK);
  83. }
  84. }
  85. break;
  86. }
  87. if(oldProc)
  88. return CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
  89. else
  90. return DefWindowProc(hWnd, uMsg, wParam, lParam);
  91. }
  92. static UINT_PTR CALLBACK FolderHookProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam){
  93. HWND hParent = GetParent(hdlg);
  94. switch(uMsg)
  95. {
  96. case WM_INITDIALOG:
  97. {
  98. LPOPENFILENAME lpofn = (LPOPENFILENAME)lParam;
  99. SendMessage(hParent, CDM_SETCONTROLTEXT, stc3, (LPARAM)dT("Folder name:"));
  100. SendMessage(hParent, CDM_HIDECONTROL, cmb1, 0);
  101. SendMessage(hParent, CDM_HIDECONTROL, stc2, 0);
  102. LONG oldProc = SetWindowLong(hParent, GWL_WNDPROC, (LONG)OKBtnFolderHackProc);
  103. SetProp(hParent, dT("OldWndProc"), (HANDLE)oldProc);
  104. SetProp(hParent, dT("OFN"), (HANDLE)lpofn);
  105. }
  106. break;
  107. case WM_NOTIFY:
  108. {
  109. LPNMHDR nmhdr = (LPNMHDR)lParam;
  110. switch(nmhdr->code)
  111. {
  112. case CDN_FOLDERCHANGE:
  113. {
  114. LPOFNOTIFY lpofn = (LPOFNOTIFY)lParam;
  115. OpenFolderDialog *ofd = (OpenFolderDialog *)lpofn->lpOFN->lCustData;
  116. #ifdef UNICODE
  117. UTF16 buf[MAX_PATH];
  118. #else
  119. char buf[MAX_PATH];
  120. #endif
  121. SendMessage(hParent, CDM_GETFOLDERPATH, sizeof(buf), (LPARAM)buf);
  122. char filePath[MAX_PATH];
  123. #ifdef UNICODE
  124. convertUTF16toUTF8(buf, filePath, sizeof(filePath));
  125. #else
  126. dStrcpy( filePath, buf );
  127. #endif
  128. // [tom, 12/8/2006] Hack to remove files from the list because
  129. // CDN_INCLUDEITEM doesn't work for regular files and folders.
  130. HWND shellView = GetDlgItem(hParent, lst2);
  131. HWND listView = FindWindowEx(shellView, 0, WC_LISTVIEW, NULL);
  132. if(listView)
  133. {
  134. // [neo, 5/15/2007 - #2994]
  135. // Utility class to take care of CoInitialize() etc so it doesnt get
  136. // called every time in isDirectory().
  137. Win32DirectoryResolver dirres;
  138. S32 count = ListView_GetItemCount(listView);
  139. for(S32 i = count - 1;i >= 0;--i)
  140. {
  141. ListView_GetItemText(listView, i, 0, buf, sizeof(buf));
  142. #ifdef UNICODE
  143. char buf2[MAX_PATH];
  144. convertUTF16toUTF8(buf, buf2, sizeof(buf2));
  145. #else
  146. char *buf2 = buf;
  147. #endif
  148. char full[MAX_PATH];
  149. dSprintf(full, sizeof(full), "%s\\%s", filePath, buf2);
  150. // [neo, 5/15/2007 - #2994]
  151. // Platform::isDirectory() will not pick up shortcuts to directories.
  152. //if(!Platform::isDirectory(full))
  153. if( !dirres.isDirectory( full ) )
  154. {
  155. ListView_DeleteItem(listView, i);
  156. }
  157. }
  158. }
  159. if(ofd->mMustExistInDir == NULL || *ofd->mMustExistInDir == 0)
  160. break;
  161. HWND hOK = GetDlgItem(hParent, IDOK);
  162. if(hOK == NULL)
  163. break;
  164. char checkPath[MAX_PATH];
  165. dSprintf(checkPath, sizeof(checkPath), "%s\\%s", filePath, ofd->mMustExistInDir);
  166. EnableWindow(hOK, Platform::isFile(checkPath));
  167. }
  168. break;
  169. }
  170. }
  171. break;
  172. }
  173. return 0;
  174. }
  175. static const U32 convertUTF16toUTF8DoubleNULL( const UTF16 *unistring, UTF8 *outbuffer, U32 len)
  176. {
  177. AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
  178. PROFILE_START(convertUTF16toUTF8DoubleNULL);
  179. U32 walked, nCodeunits, codeunitLen;
  180. UTF32 middleman;
  181. nCodeunits=0;
  182. while( ! (*unistring == '\0' && *(unistring + 1) == '\0') && nCodeunits + 3 < len )
  183. {
  184. walked = 1;
  185. middleman = oneUTF16toUTF32(unistring,&walked);
  186. codeunitLen = oneUTF32toUTF8(middleman, &outbuffer[nCodeunits]);
  187. unistring += walked;
  188. nCodeunits += codeunitLen;
  189. }
  190. nCodeunits = getMin(nCodeunits,len - 1);
  191. outbuffer[nCodeunits] = '\0';
  192. outbuffer[nCodeunits+1] = '\0';
  193. PROFILE_END();
  194. return nCodeunits;
  195. }
  196. //
  197. // Execute Method
  198. //
  199. bool FileDialog::Execute()
  200. {
  201. static char pszResult[MAX_PATH];
  202. #ifdef UNICODE
  203. UTF16 pszFile[MAX_PATH];
  204. UTF16 pszInitialDir[MAX_PATH];
  205. UTF16 pszTitle[MAX_PATH];
  206. UTF16 pszFilter[1024];
  207. UTF16 pszFileTitle[MAX_PATH];
  208. // Convert parameters to UTF16*'s
  209. if(mData.mDefaultFile != StringTable->EmptyString) convertUTF8toUTF16((UTF8 *)mData.mDefaultFile, pszFile, sizeof(pszFile));
  210. if(mData.mDefaultPath != StringTable->EmptyString) convertUTF8toUTF16((UTF8 *)mData.mDefaultPath, pszInitialDir, sizeof(pszInitialDir));
  211. if(mData.mTitle != StringTable->EmptyString) convertUTF8toUTF16((UTF8 *)mData.mTitle, pszTitle, sizeof(pszTitle));
  212. if(mData.mFilters != StringTable->EmptyString) convertUTF8toUTF16((UTF8 *)mData.mFilters, pszFilter, sizeof(pszFilter) );
  213. #else
  214. // Not Unicode, All char*'s!
  215. char pszFile[MAX_PATH];
  216. char pszFilter[1024];
  217. char pszFileTitle[MAX_PATH];
  218. dStrcpy( pszFile, mData.mDefaultFile );
  219. dStrcpy( pszFilter, mData.mFilters );
  220. const char* pszInitialDir = mData.mDefaultPath;
  221. const char* pszTitle = mData.mTitle;
  222. #endif
  223. pszFileTitle[0] = 0;
  224. // Convert Filters
  225. U8 filterLen = dStrlen( pszFilter );
  226. for( S32 i = 0; i < filterLen; i++ )
  227. {
  228. if( pszFilter[i] == '|' )
  229. pszFilter[i] = '\0';
  230. }
  231. // Add second NULL terminator at the end
  232. pszFilter[ filterLen + 1 ] = '\0';
  233. OPENFILENAME ofn;
  234. dMemset(&ofn, 0, sizeof(ofn));
  235. ofn.lStructSize = sizeof(ofn);
  236. ofn.hwndOwner = winState.appWindow;
  237. ofn.lpstrFile = pszFile;
  238. if( !dStrncmp( mData.mDefaultFile, "", 1 ) )
  239. ofn.lpstrFile[0] = '\0';
  240. ofn.nMaxFile = sizeof(pszFile);
  241. ofn.lpstrFilter = pszFilter;
  242. ofn.nFilterIndex = 1;
  243. ofn.lpstrInitialDir = pszInitialDir;
  244. ofn.lCustData = (LPARAM)this;
  245. ofn.lpstrFileTitle = pszFileTitle;
  246. ofn.nMaxFileTitle = sizeof(pszFileTitle);
  247. if( mData.mTitle != StringTable->EmptyString )
  248. ofn.lpstrTitle = pszTitle;
  249. // Build Proper Flags.
  250. ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_HIDEREADONLY;
  251. if(mData.mStyle & FileDialogData::FDS_BROWSEFOLDER)
  252. {
  253. ofn.lpfnHook = FolderHookProc;
  254. ofn.Flags |= OFN_ENABLEHOOK;
  255. }
  256. if( !(mData.mStyle & FileDialogData::FDS_CHANGEPATH) )
  257. ofn.Flags |= OFN_NOCHANGEDIR;
  258. if( mData.mStyle & FileDialogData::FDS_MUSTEXIST )
  259. ofn.Flags |= OFN_FILEMUSTEXIST;
  260. if( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )
  261. ofn.Flags |= OFN_ALLOWMULTISELECT;
  262. if( mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT )
  263. ofn.Flags |= OFN_OVERWRITEPROMPT;
  264. // Flag we're showing file browser so we can do some render hacking
  265. winState.renderThreadBlocked = true;
  266. // Execute Dialog (Blocking Call)
  267. bool dialogSuccess = false;
  268. if( mData.mStyle & FileDialogData::FDS_OPEN )
  269. dialogSuccess = GetOpenFileName(&ofn);
  270. else if( mData.mStyle & FileDialogData::FDS_SAVE )
  271. dialogSuccess = GetSaveFileName(&ofn);
  272. // Dialog is gone.
  273. winState.renderThreadBlocked = false;
  274. // Did we select a file?
  275. if( !dialogSuccess )
  276. return false;
  277. // Handle Result Properly for Unicode as well as ANSI
  278. #ifdef UNICODE
  279. if(pszFileTitle[0] || ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ))
  280. convertUTF16toUTF8( (UTF16*)pszFile, (UTF8*)pszResult, sizeof(pszResult));
  281. else
  282. convertUTF16toUTF8DoubleNULL( (UTF16*)pszFile, (UTF8*)pszResult, sizeof(pszResult));
  283. #else
  284. if(pszFileTitle[0] || ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ))
  285. dStrcpy(pszResult,pszFile);
  286. else
  287. {
  288. // [tom, 1/4/2007] pszResult is a double-NULL terminated, NULL separated list in this case so we can't just dSstrcpy()
  289. char *sptr = pszFile, *dptr = pszResult;
  290. while(! (*sptr == 0 && *(sptr+1) == 0))
  291. *dptr++ = *sptr++;
  292. *dptr++ = 0;
  293. }
  294. #endif
  295. forwardslash(pszResult);
  296. // [tom, 1/5/2007] Windows is ridiculously dumb. If you select a single file in a multiple
  297. // select file dialog then it will return the file the same way as it would in a single
  298. // select dialog. The only difference is pszFileTitle is empty if multiple files
  299. // are selected.
  300. // Store the result on our object
  301. if( mData.mStyle & FileDialogData::FDS_BROWSEFOLDER || ( pszFileTitle[0] && ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )))
  302. {
  303. // Single file selection, do it the easy way
  304. mData.mFile = StringTable->insert( pszResult );
  305. }
  306. else if(pszFileTitle[0] && ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ))
  307. {
  308. // Single file selection in a multiple file selection dialog
  309. setDataField(StringTable->insert("files"), "0", pszResult);
  310. setDataField(StringTable->insert("fileCount"), NULL, "1");
  311. }
  312. else
  313. {
  314. // Multiple file selection, break out into an array
  315. S32 numFiles = 0;
  316. const char *dir = pszResult;
  317. const char *file = dir + dStrlen(dir) + 1;
  318. char buffer[1024];
  319. while(*file)
  320. {
  321. Platform::makeFullPathName(file, buffer, sizeof(buffer), dir);
  322. setDataField(StringTable->insert("files"), Con::getIntArg(numFiles++), buffer);
  323. file = file + dStrlen(file) + 1;
  324. }
  325. setDataField(StringTable->insert("fileCount"), NULL, Con::getIntArg(numFiles));
  326. }
  327. // Return success.
  328. return true;
  329. }
  330. //-----------------------------------------------------------------------------
  331. // Default Path Property - String Validated on Write
  332. //-----------------------------------------------------------------------------
  333. bool FileDialog::setDefaultPath(void* obj, const char* data)
  334. {
  335. if( !data || !dStrncmp( data, "", 1 ) )
  336. return true;
  337. // Copy and Backslash the path (Windows dialogs are VERY picky about this format)
  338. static char szPathValidate[512];
  339. dStrcpy( szPathValidate, data );
  340. Platform::makeFullPathName( data,szPathValidate, sizeof(szPathValidate));
  341. backslash( szPathValidate );
  342. // Remove any trailing \'s
  343. S8 validateLen = dStrlen( szPathValidate );
  344. if( szPathValidate[ validateLen - 1 ] == '\\' )
  345. szPathValidate[ validateLen - 1 ] = '\0';
  346. //Luma: Add a check for windows drives. For some reason its X: and thats crashing here cos of recursive adding of a folder.
  347. if( szPathValidate[ validateLen - 1 ] == ':' )
  348. {
  349. Con::errorf(ConsoleLogEntry::GUI, "Luma :: FileDialog - Invalid Default Path Specified!");
  350. return false;
  351. }
  352. // Now check
  353. ResourceManager->addPath(szPathValidate, true);
  354. if( Platform::isDirectory( szPathValidate ) )
  355. {
  356. // Finally, assign in proper format.
  357. FileDialog *pDlg = static_cast<FileDialog*>( obj );
  358. pDlg->mData.mDefaultPath = StringTable->insert( szPathValidate );
  359. }
  360. #ifdef TORQUE_DEBUG
  361. else
  362. Con::errorf(ConsoleLogEntry::GUI, "FileDialog - Invalid Default Path Specified!");
  363. #endif
  364. return false;
  365. };
  366. //-----------------------------------------------------------------------------
  367. // Default File Property - String Validated on Write
  368. //-----------------------------------------------------------------------------
  369. bool FileDialog::setDefaultFile(void* obj, const char* data)
  370. {
  371. if( !data || !dStrncmp( data, "", 1 ) )
  372. return true;
  373. // Copy and Backslash the path (Windows dialogs are VERY picky about this format)
  374. static char szPathValidate[512];
  375. Platform::makeFullPathName( data,szPathValidate, sizeof(szPathValidate) );
  376. backslash( szPathValidate );
  377. // Remove any trailing \'s
  378. S8 validateLen = dStrlen( szPathValidate );
  379. if( szPathValidate[ validateLen - 1 ] == '\\' )
  380. szPathValidate[ validateLen - 1 ] = '\0';
  381. // Finally, assign in proper format.
  382. FileDialog *pDlg = static_cast<FileDialog*>( obj );
  383. pDlg->mData.mDefaultFile = StringTable->insert( szPathValidate );
  384. return false;
  385. };