win32FileDialog.cc 16 KB

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