nfd_win.cpp 21 KB

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