nfd_win.cpp 20 KB

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