nfd_win.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. /*
  2. Native File Dialog
  3. http://www.frogtoss.com/labs
  4. */
  5. #ifdef __MINGW32__
  6. // Explicitly setting NTDDI version, this is necessary for the MinGW compiler
  7. #define NTDDI_VERSION NTDDI_VISTA
  8. #define _WIN32_WINNT _WIN32_WINNT_VISTA
  9. #endif
  10. #define _CRTDBG_MAP_ALLOC
  11. #include <stdlib.h>
  12. #include <crtdbg.h>
  13. /* only locally define UNICODE in this compilation unit */
  14. #ifndef UNICODE
  15. #define UNICODE
  16. #endif
  17. #include <wchar.h>
  18. #include <stdio.h>
  19. #include <assert.h>
  20. #include <windows.h>
  21. #include <shobjidl.h>
  22. #include "nfd_common.h"
  23. #define COM_INITFLAGS ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE
  24. static BOOL COMIsInitialized(HRESULT coResult)
  25. {
  26. if (coResult == RPC_E_CHANGED_MODE)
  27. {
  28. // If COM was previously initialized with different init flags,
  29. // NFD still needs to operate. Eat this warning.
  30. return TRUE;
  31. }
  32. return SUCCEEDED(coResult);
  33. }
  34. static HRESULT COMInit(void)
  35. {
  36. return ::CoInitializeEx(NULL, COM_INITFLAGS);
  37. }
  38. static void COMUninit(HRESULT coResult)
  39. {
  40. // do not uninitialize if RPC_E_CHANGED_MODE occurred -- this
  41. // case does not refcount COM.
  42. if (SUCCEEDED(coResult))
  43. ::CoUninitialize();
  44. }
  45. // allocs the space in outPath -- call free()
  46. static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
  47. {
  48. int inStrCharacterCount = static_cast<int>(wcslen(inStr));
  49. int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
  50. inStr, inStrCharacterCount,
  51. NULL, 0, NULL, NULL );
  52. assert( bytesNeeded );
  53. bytesNeeded += 1;
  54. *outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded );
  55. if ( !*outStr )
  56. return;
  57. int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
  58. inStr, -1,
  59. *outStr, bytesNeeded,
  60. NULL, NULL );
  61. assert( bytesWritten ); _NFD_UNUSED( bytesWritten );
  62. }
  63. /* includes NULL terminator byte in return */
  64. static size_t GetUTF8ByteCountForWChar( const wchar_t *str )
  65. {
  66. size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
  67. str, -1,
  68. NULL, 0, NULL, NULL );
  69. assert( bytesNeeded );
  70. return bytesNeeded+1;
  71. }
  72. // write to outPtr -- no free() necessary.
  73. static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr )
  74. {
  75. int bytesNeeded = static_cast<int>(GetUTF8ByteCountForWChar( inStr ));
  76. /* invocation copies null term */
  77. int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
  78. inStr, -1,
  79. outPtr, bytesNeeded,
  80. NULL, 0 );
  81. assert( bytesWritten );
  82. return bytesWritten;
  83. }
  84. // allocs the space in outStr -- call free()
  85. static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
  86. {
  87. int inStrByteCount = static_cast<int>(strlen(inStr));
  88. int charsNeeded = MultiByteToWideChar(CP_UTF8, 0,
  89. inStr, inStrByteCount,
  90. NULL, 0 );
  91. assert( charsNeeded );
  92. assert( !*outStr );
  93. charsNeeded += 1; // terminator
  94. *outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) );
  95. if ( !*outStr )
  96. return;
  97. int ret = MultiByteToWideChar(CP_UTF8, 0,
  98. inStr, inStrByteCount,
  99. *outStr, charsNeeded);
  100. (*outStr)[charsNeeded-1] = '\0';
  101. #ifdef _DEBUG
  102. int inStrCharacterCount = static_cast<int>(NFDi_UTF8_Strlen(inStr));
  103. assert( ret == inStrCharacterCount );
  104. #else
  105. _NFD_UNUSED(ret);
  106. #endif
  107. }
  108. /* ext is in format "jpg", no wildcards or separators */
  109. static int AppendExtensionToSpecBuf( const char *ext, char *specBuf, size_t specBufLen )
  110. {
  111. const char SEP[] = ";";
  112. assert( specBufLen > strlen(ext)+3 );
  113. if ( strlen(specBuf) > 0 )
  114. {
  115. strncat( specBuf, SEP, specBufLen - strlen(specBuf) - 1 );
  116. specBufLen += strlen(SEP);
  117. }
  118. char extWildcard[NFD_MAX_STRLEN];
  119. int bytesWritten = sprintf_s( extWildcard, NFD_MAX_STRLEN, "*.%s", ext );
  120. assert( bytesWritten == (int)(strlen(ext)+2) );
  121. _NFD_UNUSED(bytesWritten);
  122. strncat( specBuf, extWildcard, specBufLen - strlen(specBuf) - 1 );
  123. return NFD_OKAY;
  124. }
  125. static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char *filterList )
  126. {
  127. const wchar_t WILDCARD[] = L"*.*";
  128. if ( !filterList || strlen(filterList) == 0 )
  129. return NFD_OKAY;
  130. // Count rows to alloc
  131. UINT filterCount = 1; /* guaranteed to have one filter on a correct, non-empty parse */
  132. const char *p_filterList;
  133. for ( p_filterList = filterList; *p_filterList; ++p_filterList )
  134. {
  135. if ( *p_filterList == ';' )
  136. ++filterCount;
  137. }
  138. assert(filterCount);
  139. if ( !filterCount )
  140. {
  141. NFDi_SetError("Error parsing filters.");
  142. return NFD_ERROR;
  143. }
  144. /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
  145. COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) );
  146. if ( !specList )
  147. {
  148. return NFD_ERROR;
  149. }
  150. for (UINT i = 0; i < filterCount+1; ++i )
  151. {
  152. specList[i].pszName = NULL;
  153. specList[i].pszSpec = NULL;
  154. }
  155. size_t specIdx = 0;
  156. p_filterList = filterList;
  157. char typebuf[NFD_MAX_STRLEN] = {0}; /* one per comma or semicolon */
  158. char *p_typebuf = typebuf;
  159. char specbuf[NFD_MAX_STRLEN] = {0}; /* one per semicolon */
  160. while ( 1 )
  161. {
  162. if ( NFDi_IsFilterSegmentChar(*p_filterList) )
  163. {
  164. /* append a type to the specbuf (pending filter) */
  165. AppendExtensionToSpecBuf( typebuf, specbuf, NFD_MAX_STRLEN );
  166. p_typebuf = typebuf;
  167. memset( typebuf, 0, sizeof(char)*NFD_MAX_STRLEN );
  168. }
  169. if ( *p_filterList == ';' || *p_filterList == '\0' )
  170. {
  171. /* end of filter -- add it to specList */
  172. CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName );
  173. CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec );
  174. memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN );
  175. ++specIdx;
  176. if ( specIdx == filterCount )
  177. break;
  178. }
  179. if ( !NFDi_IsFilterSegmentChar( *p_filterList ))
  180. {
  181. *p_typebuf = *p_filterList;
  182. ++p_typebuf;
  183. }
  184. ++p_filterList;
  185. }
  186. /* Add wildcard */
  187. specList[specIdx].pszSpec = WILDCARD;
  188. specList[specIdx].pszName = WILDCARD;
  189. fileOpenDialog->SetFileTypes( filterCount+1, specList );
  190. /* free speclist */
  191. for ( size_t i = 0; i < filterCount; ++i )
  192. {
  193. NFDi_Free( (void*)specList[i].pszSpec );
  194. }
  195. NFDi_Free( specList );
  196. return NFD_OKAY;
  197. }
  198. static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet )
  199. {
  200. const char ERRORMSG[] = "Error allocating pathset.";
  201. assert(shellItems);
  202. assert(pathSet);
  203. // How many items in shellItems?
  204. DWORD numShellItems;
  205. HRESULT result = shellItems->GetCount(&numShellItems);
  206. if ( !SUCCEEDED(result) )
  207. {
  208. NFDi_SetError(ERRORMSG);
  209. return NFD_ERROR;
  210. }
  211. pathSet->count = static_cast<size_t>(numShellItems);
  212. assert( pathSet->count > 0 );
  213. pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count );
  214. if ( !pathSet->indices )
  215. {
  216. return NFD_ERROR;
  217. }
  218. /* count the total bytes needed for buf */
  219. size_t bufSize = 0;
  220. for ( DWORD i = 0; i < numShellItems; ++i )
  221. {
  222. ::IShellItem *shellItem;
  223. result = shellItems->GetItemAt(i, &shellItem);
  224. if ( !SUCCEEDED(result) )
  225. {
  226. NFDi_SetError(ERRORMSG);
  227. return NFD_ERROR;
  228. }
  229. // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
  230. SFGAOF attribs;
  231. result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
  232. if ( !SUCCEEDED(result) )
  233. {
  234. NFDi_SetError(ERRORMSG);
  235. return NFD_ERROR;
  236. }
  237. if ( !(attribs & SFGAO_FILESYSTEM) )
  238. continue;
  239. LPWSTR name;
  240. shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
  241. // Calculate length of name with UTF-8 encoding
  242. bufSize += GetUTF8ByteCountForWChar( name );
  243. CoTaskMemFree(name);
  244. }
  245. assert(bufSize);
  246. pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
  247. memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize );
  248. /* fill buf */
  249. nfdchar_t *p_buf = pathSet->buf;
  250. for (DWORD i = 0; i < numShellItems; ++i )
  251. {
  252. ::IShellItem *shellItem;
  253. result = shellItems->GetItemAt(i, &shellItem);
  254. if ( !SUCCEEDED(result) )
  255. {
  256. NFDi_SetError(ERRORMSG);
  257. return NFD_ERROR;
  258. }
  259. // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
  260. SFGAOF attribs;
  261. result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
  262. if ( !SUCCEEDED(result) )
  263. {
  264. NFDi_SetError(ERRORMSG);
  265. return NFD_ERROR;
  266. }
  267. if ( !(attribs & SFGAO_FILESYSTEM) )
  268. continue;
  269. LPWSTR name;
  270. shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
  271. int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
  272. CoTaskMemFree(name);
  273. ptrdiff_t index = p_buf - pathSet->buf;
  274. assert( index >= 0 );
  275. pathSet->indices[i] = static_cast<size_t>(index);
  276. p_buf += bytesWritten;
  277. }
  278. return NFD_OKAY;
  279. }
  280. static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath )
  281. {
  282. if ( !defaultPath || strlen(defaultPath) == 0 )
  283. return NFD_OKAY;
  284. wchar_t *defaultPathW = {0};
  285. CopyNFDCharToWChar( defaultPath, &defaultPathW );
  286. IShellItem *folder;
  287. HRESULT result = SHCreateItemFromParsingName( defaultPathW, NULL, IID_PPV_ARGS(&folder) );
  288. // Valid non results.
  289. if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) )
  290. {
  291. NFDi_Free( defaultPathW );
  292. return NFD_OKAY;
  293. }
  294. if ( !SUCCEEDED(result) )
  295. {
  296. NFDi_SetError("Error creating ShellItem");
  297. NFDi_Free( defaultPathW );
  298. return NFD_ERROR;
  299. }
  300. // Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API.
  301. dialog->SetFolder( folder );
  302. NFDi_Free( defaultPathW );
  303. folder->Release();
  304. return NFD_OKAY;
  305. }
  306. /* public */
  307. nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
  308. const nfdchar_t *defaultPath,
  309. nfdchar_t **outPath )
  310. {
  311. nfdresult_t nfdResult = NFD_ERROR;
  312. HRESULT coResult = COMInit();
  313. if (!COMIsInitialized(coResult))
  314. {
  315. NFDi_SetError("Could not initialize COM.");
  316. return nfdResult;
  317. }
  318. // Create dialog
  319. ::IFileOpenDialog *fileOpenDialog(NULL);
  320. HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
  321. CLSCTX_ALL, ::IID_IFileOpenDialog,
  322. reinterpret_cast<void**>(&fileOpenDialog) );
  323. if ( !SUCCEEDED(result) )
  324. {
  325. NFDi_SetError("Could not create dialog.");
  326. goto end;
  327. }
  328. // Build the filter list
  329. if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
  330. {
  331. goto end;
  332. }
  333. // Set the default path
  334. if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
  335. {
  336. goto end;
  337. }
  338. // Show the dialog.
  339. result = fileOpenDialog->Show(NULL);
  340. if ( SUCCEEDED(result) )
  341. {
  342. // Get the file name
  343. ::IShellItem *shellItem(NULL);
  344. result = fileOpenDialog->GetResult(&shellItem);
  345. if ( !SUCCEEDED(result) )
  346. {
  347. NFDi_SetError("Could not get shell item from dialog.");
  348. goto end;
  349. }
  350. wchar_t *filePath(NULL);
  351. result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
  352. if ( !SUCCEEDED(result) )
  353. {
  354. NFDi_SetError("Could not get file path for selected.");
  355. shellItem->Release();
  356. goto end;
  357. }
  358. CopyWCharToNFDChar( filePath, outPath );
  359. CoTaskMemFree(filePath);
  360. if ( !*outPath )
  361. {
  362. /* error is malloc-based, error message would be redundant */
  363. shellItem->Release();
  364. goto end;
  365. }
  366. nfdResult = NFD_OKAY;
  367. shellItem->Release();
  368. }
  369. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  370. {
  371. nfdResult = NFD_CANCEL;
  372. }
  373. else
  374. {
  375. NFDi_SetError("File dialog box show failed.");
  376. nfdResult = NFD_ERROR;
  377. }
  378. end:
  379. if (fileOpenDialog)
  380. fileOpenDialog->Release();
  381. COMUninit(coResult);
  382. return nfdResult;
  383. }
  384. nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
  385. const nfdchar_t *defaultPath,
  386. nfdpathset_t *outPaths )
  387. {
  388. nfdresult_t nfdResult = NFD_ERROR;
  389. HRESULT coResult = COMInit();
  390. if (!COMIsInitialized(coResult))
  391. {
  392. NFDi_SetError("Could not initialize COM.");
  393. return nfdResult;
  394. }
  395. // Create dialog
  396. ::IFileOpenDialog *fileOpenDialog(NULL);
  397. HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
  398. CLSCTX_ALL, ::IID_IFileOpenDialog,
  399. reinterpret_cast<void**>(&fileOpenDialog) );
  400. if ( !SUCCEEDED(result) )
  401. {
  402. fileOpenDialog = NULL;
  403. NFDi_SetError("Could not create dialog.");
  404. goto end;
  405. }
  406. // Build the filter list
  407. if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
  408. {
  409. goto end;
  410. }
  411. // Set the default path
  412. if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
  413. {
  414. goto end;
  415. }
  416. // Set a flag for multiple options
  417. DWORD dwFlags;
  418. result = fileOpenDialog->GetOptions(&dwFlags);
  419. if ( !SUCCEEDED(result) )
  420. {
  421. NFDi_SetError("Could not get options.");
  422. goto end;
  423. }
  424. result = fileOpenDialog->SetOptions(dwFlags | FOS_ALLOWMULTISELECT);
  425. if ( !SUCCEEDED(result) )
  426. {
  427. NFDi_SetError("Could not set options.");
  428. goto end;
  429. }
  430. // Show the dialog.
  431. result = fileOpenDialog->Show(NULL);
  432. if ( SUCCEEDED(result) )
  433. {
  434. IShellItemArray *shellItems;
  435. result = fileOpenDialog->GetResults( &shellItems );
  436. if ( !SUCCEEDED(result) )
  437. {
  438. NFDi_SetError("Could not get shell items.");
  439. goto end;
  440. }
  441. if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
  442. {
  443. shellItems->Release();
  444. goto end;
  445. }
  446. shellItems->Release();
  447. nfdResult = NFD_OKAY;
  448. }
  449. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  450. {
  451. nfdResult = NFD_CANCEL;
  452. }
  453. else
  454. {
  455. NFDi_SetError("File dialog box show failed.");
  456. nfdResult = NFD_ERROR;
  457. }
  458. end:
  459. if ( fileOpenDialog )
  460. fileOpenDialog->Release();
  461. COMUninit(coResult);
  462. return nfdResult;
  463. }
  464. nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
  465. const nfdchar_t *defaultPath,
  466. nfdchar_t **outPath )
  467. {
  468. nfdresult_t nfdResult = NFD_ERROR;
  469. HRESULT coResult = COMInit();
  470. if (!COMIsInitialized(coResult))
  471. {
  472. NFDi_SetError("Could not initialize COM.");
  473. return nfdResult;
  474. }
  475. // Create dialog
  476. ::IFileSaveDialog *fileSaveDialog(NULL);
  477. HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL,
  478. CLSCTX_ALL, ::IID_IFileSaveDialog,
  479. reinterpret_cast<void**>(&fileSaveDialog) );
  480. if ( !SUCCEEDED(result) )
  481. {
  482. fileSaveDialog = NULL;
  483. NFDi_SetError("Could not create dialog.");
  484. goto end;
  485. }
  486. // Build the filter list
  487. if ( !AddFiltersToDialog( fileSaveDialog, filterList ) )
  488. {
  489. goto end;
  490. }
  491. // Set the default path
  492. if ( !SetDefaultPath( fileSaveDialog, defaultPath ) )
  493. {
  494. goto end;
  495. }
  496. // Show the dialog.
  497. result = fileSaveDialog->Show(NULL);
  498. if ( SUCCEEDED(result) )
  499. {
  500. // Get the file name
  501. ::IShellItem *shellItem;
  502. result = fileSaveDialog->GetResult(&shellItem);
  503. if ( !SUCCEEDED(result) )
  504. {
  505. NFDi_SetError("Could not get shell item from dialog.");
  506. goto end;
  507. }
  508. wchar_t *filePath(NULL);
  509. result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
  510. if ( !SUCCEEDED(result) )
  511. {
  512. shellItem->Release();
  513. NFDi_SetError("Could not get file path for selected.");
  514. goto end;
  515. }
  516. CopyWCharToNFDChar( filePath, outPath );
  517. CoTaskMemFree(filePath);
  518. if ( !*outPath )
  519. {
  520. /* error is malloc-based, error message would be redundant */
  521. shellItem->Release();
  522. goto end;
  523. }
  524. nfdResult = NFD_OKAY;
  525. shellItem->Release();
  526. }
  527. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  528. {
  529. nfdResult = NFD_CANCEL;
  530. }
  531. else
  532. {
  533. NFDi_SetError("File dialog box show failed.");
  534. nfdResult = NFD_ERROR;
  535. }
  536. end:
  537. if ( fileSaveDialog )
  538. fileSaveDialog->Release();
  539. COMUninit(coResult);
  540. return nfdResult;
  541. }
  542. nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
  543. nfdchar_t **outPath)
  544. {
  545. nfdresult_t nfdResult = NFD_ERROR;
  546. DWORD dwOptions = 0;
  547. HRESULT coResult = COMInit();
  548. if (!COMIsInitialized(coResult))
  549. {
  550. NFDi_SetError("CoInitializeEx failed.");
  551. return nfdResult;
  552. }
  553. // Create dialog
  554. ::IFileOpenDialog *fileDialog(NULL);
  555. HRESULT result = CoCreateInstance(CLSID_FileOpenDialog,
  556. NULL,
  557. CLSCTX_ALL,
  558. IID_PPV_ARGS(&fileDialog));
  559. if ( !SUCCEEDED(result) )
  560. {
  561. NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed.");
  562. goto end;
  563. }
  564. // Set the default path
  565. if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY)
  566. {
  567. NFDi_SetError("SetDefaultPath failed.");
  568. goto end;
  569. }
  570. // Get the dialogs options
  571. if (!SUCCEEDED(fileDialog->GetOptions(&dwOptions)))
  572. {
  573. NFDi_SetError("GetOptions for IFileDialog failed.");
  574. goto end;
  575. }
  576. // Add in FOS_PICKFOLDERS which hides files and only allows selection of folders
  577. if (!SUCCEEDED(fileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS)))
  578. {
  579. NFDi_SetError("SetOptions for IFileDialog failed.");
  580. goto end;
  581. }
  582. // Show the dialog to the user
  583. result = fileDialog->Show(NULL);
  584. if ( SUCCEEDED(result) )
  585. {
  586. // Get the folder name
  587. ::IShellItem *shellItem(NULL);
  588. result = fileDialog->GetResult(&shellItem);
  589. if ( !SUCCEEDED(result) )
  590. {
  591. NFDi_SetError("Could not get file path for selected.");
  592. shellItem->Release();
  593. goto end;
  594. }
  595. wchar_t *path = NULL;
  596. result = shellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path);
  597. if ( !SUCCEEDED(result) )
  598. {
  599. NFDi_SetError("GetDisplayName for IShellItem failed.");
  600. shellItem->Release();
  601. goto end;
  602. }
  603. CopyWCharToNFDChar(path, outPath);
  604. CoTaskMemFree(path);
  605. if ( !*outPath )
  606. {
  607. shellItem->Release();
  608. goto end;
  609. }
  610. nfdResult = NFD_OKAY;
  611. shellItem->Release();
  612. }
  613. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  614. {
  615. nfdResult = NFD_CANCEL;
  616. }
  617. else
  618. {
  619. NFDi_SetError("Show for IFileDialog failed.");
  620. nfdResult = NFD_ERROR;
  621. }
  622. end:
  623. if (fileDialog)
  624. fileDialog->Release();
  625. COMUninit(coResult);
  626. return nfdResult;
  627. }