nfd.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275
  1. // This software is provided 'as-is', without any express or implied
  2. // warranty. In no event will the authors be held liable for any damages
  3. // arising from the use of this software.
  4. // Permission is granted to anyone to use this software for any purpose,
  5. // including commercial applications, and to alter it and redistribute it
  6. // freely, subject to the following restrictions:
  7. // 1. The origin of this software must not be misrepresented; you must not
  8. // claim that you wrote the original software. If you use this software
  9. // in a product, an acknowledgment in the product documentation would be
  10. // appreciated but is not required.
  11. // 2. Altered source versions must be plainly marked as such, and must not be
  12. // misrepresented as being the original software.
  13. // 3. This notice may not be removed or altered from any source distribution.
  14. // https://github.com/mlabbe/nativefiledialog
  15. #include <stdlib.h>
  16. #include <assert.h>
  17. #include <string.h>
  18. #include <stdio.h>
  19. #include "nfd.h"
  20. #include "../iron_global.h"
  21. static char g_errorstr[NFD_MAX_STRLEN] = {0};
  22. /* public routines */
  23. const char *NFD_GetError( void )
  24. {
  25. return g_errorstr;
  26. }
  27. size_t NFD_PathSet_GetCount( const nfdpathset_t *pathset )
  28. {
  29. assert(pathset);
  30. return pathset->count;
  31. }
  32. nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathset, size_t num )
  33. {
  34. assert(pathset);
  35. assert(num < pathset->count);
  36. return pathset->buf + pathset->indices[num];
  37. }
  38. void NFD_PathSet_Free( nfdpathset_t *pathset )
  39. {
  40. assert(pathset);
  41. NFDi_Free( pathset->indices );
  42. NFDi_Free( pathset->buf );
  43. }
  44. /* internal routines */
  45. void *NFDi_Malloc( size_t bytes )
  46. {
  47. void *ptr = malloc(bytes);
  48. if ( !ptr )
  49. NFDi_SetError("NFDi_Malloc failed.");
  50. return ptr;
  51. }
  52. void NFDi_Free( void *ptr )
  53. {
  54. assert(ptr);
  55. free(ptr);
  56. }
  57. void NFDi_SetError( const char *msg )
  58. {
  59. int bTruncate = NFDi_SafeStrncpy( g_errorstr, msg, NFD_MAX_STRLEN );
  60. assert( !bTruncate ); _NFD_UNUSED(bTruncate);
  61. }
  62. int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy )
  63. {
  64. size_t n = maxCopy;
  65. char *d = dst;
  66. assert( src );
  67. assert( dst );
  68. while ( n > 0 && *src != '\0' )
  69. {
  70. *d++ = *src++;
  71. --n;
  72. }
  73. /* Truncation case -
  74. terminate string and return true */
  75. if ( n == 0 )
  76. {
  77. dst[maxCopy-1] = '\0';
  78. return 1;
  79. }
  80. /* No truncation. Append a single NULL and return. */
  81. *d = '\0';
  82. return 0;
  83. }
  84. /* adapted from microutf8 */
  85. int32_t NFDi_UTF8_Strlen( const nfdchar_t *str )
  86. {
  87. /* This function doesn't properly check validity of UTF-8 character
  88. sequence, it is supposed to use only with valid UTF-8 strings. */
  89. int32_t character_count = 0;
  90. int32_t i = 0; /* Counter used to iterate over string. */
  91. nfdchar_t maybe_bom[4];
  92. /* If there is UTF-8 BOM ignore it. */
  93. if (strlen(str) > 2)
  94. {
  95. strncpy(maybe_bom, str, 3);
  96. maybe_bom[3] = 0;
  97. if (strcmp(maybe_bom, (nfdchar_t*)NFD_UTF8_BOM) == 0)
  98. i += 3;
  99. }
  100. while(str[i])
  101. {
  102. if (str[i] >> 7 == 0)
  103. {
  104. /* If bit pattern begins with 0 we have ascii character. */
  105. ++character_count;
  106. }
  107. else if (str[i] >> 6 == 3)
  108. {
  109. /* If bit pattern begins with 11 it is beginning of UTF-8 byte sequence. */
  110. ++character_count;
  111. }
  112. else if (str[i] >> 6 == 2)
  113. ; /* If bit pattern begins with 10 it is middle of utf-8 byte sequence. */
  114. else
  115. {
  116. /* In any other case this is not valid UTF-8. */
  117. return -1;
  118. }
  119. ++i;
  120. }
  121. return character_count;
  122. }
  123. int NFDi_IsFilterSegmentChar( char ch )
  124. {
  125. return (ch==','||ch==';'||ch=='\0');
  126. }
  127. #ifdef IRON_LINUX
  128. #include <gtk/gtk.h>
  129. const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+";
  130. static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
  131. {
  132. const char SEP[] = ", ";
  133. size_t len = strlen(filterName);
  134. if ( len != 0 )
  135. {
  136. strncat( filterName, SEP, bufsize - len - 1 );
  137. len += strlen(SEP);
  138. }
  139. strncat( filterName, typebuf, bufsize - len - 1 );
  140. }
  141. static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList )
  142. {
  143. GtkFileFilter *filter;
  144. char typebuf[NFD_MAX_STRLEN] = {0};
  145. const char *p_filterList = filterList;
  146. char *p_typebuf = typebuf;
  147. char filterName[NFD_MAX_STRLEN] = {0};
  148. if ( !filterList || strlen(filterList) == 0 )
  149. return;
  150. filter = gtk_file_filter_new();
  151. while ( 1 )
  152. {
  153. if ( NFDi_IsFilterSegmentChar(*p_filterList) )
  154. {
  155. char typebufWildcard[NFD_MAX_STRLEN];
  156. /* add another type to the filter */
  157. assert( strlen(typebuf) > 0 );
  158. assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
  159. snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
  160. AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
  161. gtk_file_filter_add_pattern( filter, typebufWildcard );
  162. p_typebuf = typebuf;
  163. memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
  164. }
  165. if ( *p_filterList == ';' || *p_filterList == '\0' )
  166. {
  167. /* end of filter -- add it to the dialog */
  168. gtk_file_filter_set_name( filter, filterName );
  169. gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
  170. filterName[0] = '\0';
  171. if ( *p_filterList == '\0' )
  172. break;
  173. filter = gtk_file_filter_new();
  174. }
  175. if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
  176. {
  177. *p_typebuf = *p_filterList;
  178. p_typebuf++;
  179. }
  180. p_filterList++;
  181. }
  182. /* always append a wildcard option to the end*/
  183. filter = gtk_file_filter_new();
  184. gtk_file_filter_set_name( filter, "*.*" );
  185. gtk_file_filter_add_pattern( filter, "*" );
  186. gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
  187. }
  188. static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath )
  189. {
  190. if ( !defaultPath || strlen(defaultPath) == 0 )
  191. return;
  192. /* GTK+ manual recommends not specifically setting the default path.
  193. We do it anyway in order to be consistent across platforms.
  194. If consistency with the native OS is preferred, this is the line
  195. to comment out. -ml */
  196. gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath );
  197. }
  198. static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet )
  199. {
  200. size_t bufSize = 0;
  201. GSList *node;
  202. nfdchar_t *p_buf;
  203. size_t count = 0;
  204. assert(fileList);
  205. assert(pathSet);
  206. pathSet->count = (size_t)g_slist_length( fileList );
  207. assert( pathSet->count > 0 );
  208. pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
  209. if ( !pathSet->indices )
  210. {
  211. return NFD_ERROR;
  212. }
  213. /* count the total space needed for buf */
  214. for ( node = fileList; node; node = node->next )
  215. {
  216. assert(node->data);
  217. bufSize += strlen( (const gchar*)node->data ) + 1;
  218. }
  219. pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
  220. /* fill buf */
  221. p_buf = pathSet->buf;
  222. for ( node = fileList; node; node = node->next )
  223. {
  224. nfdchar_t *path = (nfdchar_t*)(node->data);
  225. size_t byteLen = strlen(path)+1;
  226. ptrdiff_t index;
  227. memcpy( p_buf, path, byteLen );
  228. g_free(node->data);
  229. index = p_buf - pathSet->buf;
  230. assert( index >= 0 );
  231. pathSet->indices[count] = (size_t)index;
  232. p_buf += byteLen;
  233. ++count;
  234. }
  235. g_slist_free( fileList );
  236. return NFD_OKAY;
  237. }
  238. static void WaitForCleanup(void)
  239. {
  240. while (gtk_events_pending())
  241. gtk_main_iteration();
  242. }
  243. /* public */
  244. nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
  245. const nfdchar_t *defaultPath,
  246. nfdchar_t **outPath )
  247. {
  248. GtkWidget *dialog;
  249. nfdresult_t result;
  250. if ( !gtk_init_check( NULL, NULL ) )
  251. {
  252. NFDi_SetError(INIT_FAIL_MSG);
  253. return NFD_ERROR;
  254. }
  255. dialog = gtk_file_chooser_dialog_new( "Open File",
  256. NULL,
  257. GTK_FILE_CHOOSER_ACTION_OPEN,
  258. "_Cancel", GTK_RESPONSE_CANCEL,
  259. "_Open", GTK_RESPONSE_ACCEPT,
  260. NULL );
  261. /* Build the filter list */
  262. AddFiltersToDialog(dialog, filterList);
  263. /* Set the default path */
  264. SetDefaultPath(dialog, defaultPath);
  265. result = NFD_CANCEL;
  266. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  267. {
  268. char *filename;
  269. filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
  270. {
  271. size_t len = strlen(filename);
  272. *outPath = NFDi_Malloc( len + 1 );
  273. memcpy( *outPath, filename, len + 1 );
  274. if ( !*outPath )
  275. {
  276. g_free( filename );
  277. gtk_widget_destroy(dialog);
  278. return NFD_ERROR;
  279. }
  280. }
  281. g_free( filename );
  282. result = NFD_OKAY;
  283. }
  284. WaitForCleanup();
  285. gtk_widget_destroy(dialog);
  286. WaitForCleanup();
  287. return result;
  288. }
  289. nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
  290. const nfdchar_t *defaultPath,
  291. nfdpathset_t *outPaths )
  292. {
  293. GtkWidget *dialog;
  294. nfdresult_t result;
  295. if ( !gtk_init_check( NULL, NULL ) )
  296. {
  297. NFDi_SetError(INIT_FAIL_MSG);
  298. return NFD_ERROR;
  299. }
  300. dialog = gtk_file_chooser_dialog_new( "Open Files",
  301. NULL,
  302. GTK_FILE_CHOOSER_ACTION_OPEN,
  303. "_Cancel", GTK_RESPONSE_CANCEL,
  304. "_Open", GTK_RESPONSE_ACCEPT,
  305. NULL );
  306. gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE );
  307. /* Build the filter list */
  308. AddFiltersToDialog(dialog, filterList);
  309. /* Set the default path */
  310. SetDefaultPath(dialog, defaultPath);
  311. result = NFD_CANCEL;
  312. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  313. {
  314. GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
  315. if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR )
  316. {
  317. gtk_widget_destroy(dialog);
  318. return NFD_ERROR;
  319. }
  320. result = NFD_OKAY;
  321. }
  322. WaitForCleanup();
  323. gtk_widget_destroy(dialog);
  324. WaitForCleanup();
  325. return result;
  326. }
  327. nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
  328. const nfdchar_t *defaultPath,
  329. nfdchar_t **outPath )
  330. {
  331. GtkWidget *dialog;
  332. nfdresult_t result;
  333. if ( !gtk_init_check( NULL, NULL ) )
  334. {
  335. NFDi_SetError(INIT_FAIL_MSG);
  336. return NFD_ERROR;
  337. }
  338. dialog = gtk_file_chooser_dialog_new( "Save File",
  339. NULL,
  340. GTK_FILE_CHOOSER_ACTION_SAVE,
  341. "_Cancel", GTK_RESPONSE_CANCEL,
  342. "_Save", GTK_RESPONSE_ACCEPT,
  343. NULL );
  344. gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
  345. /* Build the filter list */
  346. AddFiltersToDialog(dialog, filterList);
  347. /* Set the default path */
  348. SetDefaultPath(dialog, defaultPath);
  349. result = NFD_CANCEL;
  350. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  351. {
  352. char *filename;
  353. filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
  354. {
  355. size_t len = strlen(filename);
  356. *outPath = NFDi_Malloc( len + 1 );
  357. memcpy( *outPath, filename, len + 1 );
  358. if ( !*outPath )
  359. {
  360. g_free( filename );
  361. gtk_widget_destroy(dialog);
  362. return NFD_ERROR;
  363. }
  364. }
  365. g_free(filename);
  366. result = NFD_OKAY;
  367. }
  368. WaitForCleanup();
  369. gtk_widget_destroy(dialog);
  370. WaitForCleanup();
  371. return result;
  372. }
  373. nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
  374. nfdchar_t **outPath)
  375. {
  376. GtkWidget *dialog;
  377. nfdresult_t result;
  378. if (!gtk_init_check(NULL, NULL))
  379. {
  380. NFDi_SetError(INIT_FAIL_MSG);
  381. return NFD_ERROR;
  382. }
  383. dialog = gtk_file_chooser_dialog_new( "Select folder",
  384. NULL,
  385. GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
  386. "_Cancel", GTK_RESPONSE_CANCEL,
  387. "_Select", GTK_RESPONSE_ACCEPT,
  388. NULL );
  389. gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
  390. /* Set the default path */
  391. SetDefaultPath(dialog, defaultPath);
  392. result = NFD_CANCEL;
  393. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  394. {
  395. char *filename;
  396. filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
  397. {
  398. size_t len = strlen(filename);
  399. *outPath = NFDi_Malloc( len + 1 );
  400. memcpy( *outPath, filename, len + 1 );
  401. if ( !*outPath )
  402. {
  403. g_free( filename );
  404. gtk_widget_destroy(dialog);
  405. return NFD_ERROR;
  406. }
  407. }
  408. g_free(filename);
  409. result = NFD_OKAY;
  410. }
  411. WaitForCleanup();
  412. gtk_widget_destroy(dialog);
  413. WaitForCleanup();
  414. return result;
  415. }
  416. #endif
  417. #ifdef IRON_WINDOWS
  418. #ifdef __MINGW32__
  419. // Explicitly setting NTDDI version, this is necessary for the MinGW compiler
  420. #define NTDDI_VERSION NTDDI_VISTA
  421. #define _WIN32_WINNT _WIN32_WINNT_VISTA
  422. #endif
  423. #define _CRTDBG_MAP_ALLOC
  424. #include <stdlib.h>
  425. #include <crtdbg.h>
  426. /* only locally define UNICODE in this compilation unit */
  427. #ifndef UNICODE
  428. #define UNICODE
  429. #endif
  430. #include <wchar.h>
  431. #include <stdio.h>
  432. #include <assert.h>
  433. #include <windows.h>
  434. #include <shobjidl.h>
  435. #define COM_INITFLAGS COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE
  436. static BOOL COMIsInitialized(HRESULT coResult)
  437. {
  438. if (coResult == RPC_E_CHANGED_MODE)
  439. {
  440. // If COM was previously initialized with different init flags,
  441. // NFD still needs to operate. Eat this warning.
  442. return TRUE;
  443. }
  444. return SUCCEEDED(coResult);
  445. }
  446. static HRESULT COMInit(void)
  447. {
  448. return CoInitializeEx(NULL, COM_INITFLAGS);
  449. }
  450. static void COMUninit(HRESULT coResult)
  451. {
  452. // do not uninitialize if RPC_E_CHANGED_MODE occurred -- this
  453. // case does not refcount COM.
  454. if (SUCCEEDED(coResult))
  455. CoUninitialize();
  456. }
  457. // allocs the space in outPath -- call free()
  458. static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
  459. {
  460. int inStrCharacterCount = (int)(wcslen(inStr));
  461. int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
  462. inStr, inStrCharacterCount,
  463. NULL, 0, NULL, NULL );
  464. assert( bytesNeeded );
  465. bytesNeeded += 1;
  466. *outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded );
  467. if ( !*outStr )
  468. return;
  469. int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
  470. inStr, -1,
  471. *outStr, bytesNeeded,
  472. NULL, NULL );
  473. assert( bytesWritten ); _NFD_UNUSED( bytesWritten );
  474. }
  475. /* includes NULL terminator byte in return */
  476. static size_t GetUTF8ByteCountForWChar( const wchar_t *str )
  477. {
  478. size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
  479. str, -1,
  480. NULL, 0, NULL, NULL );
  481. assert( bytesNeeded );
  482. return bytesNeeded+1;
  483. }
  484. // write to outPtr -- no free() necessary.
  485. static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr )
  486. {
  487. int bytesNeeded = (int)(GetUTF8ByteCountForWChar( inStr ));
  488. /* invocation copies null term */
  489. int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
  490. inStr, -1,
  491. outPtr, bytesNeeded,
  492. NULL, 0 );
  493. assert( bytesWritten );
  494. return bytesWritten;
  495. }
  496. // allocs the space in outStr -- call free()
  497. static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
  498. {
  499. int inStrByteCount = (int)(strlen(inStr));
  500. int charsNeeded = MultiByteToWideChar(CP_UTF8, 0,
  501. inStr, inStrByteCount,
  502. NULL, 0 );
  503. assert( charsNeeded );
  504. assert( !*outStr );
  505. charsNeeded += 1; // terminator
  506. *outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) );
  507. if ( !*outStr )
  508. return;
  509. int ret = MultiByteToWideChar(CP_UTF8, 0,
  510. inStr, inStrByteCount,
  511. *outStr, charsNeeded);
  512. (*outStr)[charsNeeded-1] = '\0';
  513. #ifdef _DEBUG
  514. int inStrCharacterCount = (int)(NFDi_UTF8_Strlen(inStr));
  515. assert( ret == inStrCharacterCount );
  516. #else
  517. _NFD_UNUSED(ret);
  518. #endif
  519. }
  520. /* ext is in format "jpg", no wildcards or separators */
  521. static int AppendExtensionToSpecBuf( const char *ext, char *specBuf, size_t specBufLen )
  522. {
  523. const char SEP[] = ";";
  524. assert( specBufLen > strlen(ext)+3 );
  525. if ( strlen(specBuf) > 0 )
  526. {
  527. strncat( specBuf, SEP, specBufLen - strlen(specBuf) - 1 );
  528. specBufLen += strlen(SEP);
  529. }
  530. char extWildcard[NFD_MAX_STRLEN];
  531. int bytesWritten = sprintf_s( extWildcard, NFD_MAX_STRLEN, "*.%s", ext );
  532. assert( bytesWritten == (int)(strlen(ext)+2) );
  533. _NFD_UNUSED(bytesWritten);
  534. strncat( specBuf, extWildcard, specBufLen - strlen(specBuf) - 1 );
  535. return NFD_OKAY;
  536. }
  537. static nfdresult_t AddFiltersToDialog( IFileDialog *fileOpenDialog, const char *filterList )
  538. {
  539. const wchar_t WILDCARD[] = L"*.*";
  540. if ( !filterList || strlen(filterList) == 0 )
  541. return NFD_OKAY;
  542. // Count rows to alloc
  543. UINT filterCount = 1; /* guaranteed to have one filter on a correct, non-empty parse */
  544. const char *p_filterList;
  545. for ( p_filterList = filterList; *p_filterList; ++p_filterList )
  546. {
  547. if ( *p_filterList == ';' )
  548. ++filterCount;
  549. }
  550. assert(filterCount);
  551. if ( !filterCount )
  552. {
  553. NFDi_SetError("Error parsing filters.");
  554. return NFD_ERROR;
  555. }
  556. /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
  557. COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) );
  558. if ( !specList )
  559. {
  560. return NFD_ERROR;
  561. }
  562. for (UINT i = 0; i < filterCount+1; ++i )
  563. {
  564. specList[i].pszName = NULL;
  565. specList[i].pszSpec = NULL;
  566. }
  567. size_t specIdx = 0;
  568. p_filterList = filterList;
  569. char typebuf[NFD_MAX_STRLEN] = {0}; /* one per comma or semicolon */
  570. char *p_typebuf = typebuf;
  571. char specbuf[NFD_MAX_STRLEN] = {0}; /* one per semicolon */
  572. while ( 1 )
  573. {
  574. if ( NFDi_IsFilterSegmentChar(*p_filterList) )
  575. {
  576. /* append a type to the specbuf (pending filter) */
  577. AppendExtensionToSpecBuf( typebuf, specbuf, NFD_MAX_STRLEN );
  578. p_typebuf = typebuf;
  579. memset( typebuf, 0, sizeof(char)*NFD_MAX_STRLEN );
  580. }
  581. if ( *p_filterList == ';' || *p_filterList == '\0' )
  582. {
  583. /* end of filter -- add it to specList */
  584. CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName );
  585. CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec );
  586. memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN );
  587. ++specIdx;
  588. if ( specIdx == filterCount )
  589. break;
  590. }
  591. if ( !NFDi_IsFilterSegmentChar( *p_filterList ))
  592. {
  593. *p_typebuf = *p_filterList;
  594. ++p_typebuf;
  595. }
  596. ++p_filterList;
  597. }
  598. /* Add wildcard */
  599. specList[specIdx].pszSpec = WILDCARD;
  600. specList[specIdx].pszName = WILDCARD;
  601. fileOpenDialog->lpVtbl->SetFileTypes(fileOpenDialog, filterCount + 1, specList);
  602. /* free speclist */
  603. for ( size_t i = 0; i < filterCount; ++i )
  604. {
  605. NFDi_Free( (void*)specList[i].pszSpec );
  606. }
  607. NFDi_Free( specList );
  608. return NFD_OKAY;
  609. }
  610. static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet )
  611. {
  612. const char ERRORMSG[] = "Error allocating pathset.";
  613. assert(shellItems);
  614. assert(pathSet);
  615. // How many items in shellItems?
  616. DWORD numShellItems;
  617. HRESULT result = shellItems->lpVtbl->GetCount(shellItems, & numShellItems);
  618. if ( !SUCCEEDED(result) )
  619. {
  620. NFDi_SetError(ERRORMSG);
  621. return NFD_ERROR;
  622. }
  623. pathSet->count = (size_t)(numShellItems);
  624. assert( pathSet->count > 0 );
  625. pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count );
  626. if ( !pathSet->indices )
  627. {
  628. return NFD_ERROR;
  629. }
  630. /* count the total bytes needed for buf */
  631. size_t bufSize = 0;
  632. for ( DWORD i = 0; i < numShellItems; ++i )
  633. {
  634. IShellItem *shellItem;
  635. result = shellItems->lpVtbl->GetItemAt(shellItems, i, &shellItem);
  636. if ( !SUCCEEDED(result) )
  637. {
  638. NFDi_SetError(ERRORMSG);
  639. return NFD_ERROR;
  640. }
  641. // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
  642. SFGAOF attribs;
  643. result = shellItem->lpVtbl->GetAttributes(shellItem, SFGAO_FILESYSTEM, &attribs);
  644. if ( !SUCCEEDED(result) )
  645. {
  646. NFDi_SetError(ERRORMSG);
  647. return NFD_ERROR;
  648. }
  649. if ( !(attribs & SFGAO_FILESYSTEM) )
  650. continue;
  651. LPWSTR name;
  652. shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &name);
  653. // Calculate length of name with UTF-8 encoding
  654. bufSize += GetUTF8ByteCountForWChar( name );
  655. CoTaskMemFree(name);
  656. }
  657. assert(bufSize);
  658. pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
  659. memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize );
  660. /* fill buf */
  661. nfdchar_t *p_buf = pathSet->buf;
  662. for (DWORD i = 0; i < numShellItems; ++i )
  663. {
  664. IShellItem *shellItem;
  665. result = shellItems->lpVtbl->GetItemAt(shellItems, i, &shellItem);
  666. if ( !SUCCEEDED(result) )
  667. {
  668. NFDi_SetError(ERRORMSG);
  669. return NFD_ERROR;
  670. }
  671. // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
  672. SFGAOF attribs;
  673. result = shellItem->lpVtbl->GetAttributes(shellItem, SFGAO_FILESYSTEM, &attribs);
  674. if ( !SUCCEEDED(result) )
  675. {
  676. NFDi_SetError(ERRORMSG);
  677. return NFD_ERROR;
  678. }
  679. if ( !(attribs & SFGAO_FILESYSTEM) )
  680. continue;
  681. LPWSTR name;
  682. shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &name);
  683. int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
  684. CoTaskMemFree(name);
  685. ptrdiff_t index = p_buf - pathSet->buf;
  686. assert( index >= 0 );
  687. pathSet->indices[i] = (size_t)(index);
  688. p_buf += bytesWritten;
  689. }
  690. return NFD_OKAY;
  691. }
  692. static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath )
  693. {
  694. if ( !defaultPath || strlen(defaultPath) == 0 )
  695. return NFD_OKAY;
  696. wchar_t *defaultPathW = {0};
  697. CopyNFDCharToWChar( defaultPath, &defaultPathW );
  698. IShellItem *folder;
  699. HRESULT result = SHCreateItemFromParsingName(defaultPathW, NULL, &IID_IShellItem, (void **)&folder);
  700. // Valid non results.
  701. if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) )
  702. {
  703. NFDi_Free( defaultPathW );
  704. return NFD_OKAY;
  705. }
  706. if ( !SUCCEEDED(result) )
  707. {
  708. NFDi_SetError("Error creating ShellItem");
  709. NFDi_Free( defaultPathW );
  710. return NFD_ERROR;
  711. }
  712. // Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API.
  713. dialog->lpVtbl->SetFolder(dialog, folder);
  714. NFDi_Free( defaultPathW );
  715. folder->lpVtbl->Release(folder);
  716. return NFD_OKAY;
  717. }
  718. /* public */
  719. nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
  720. const nfdchar_t *defaultPath,
  721. nfdchar_t **outPath )
  722. {
  723. nfdresult_t nfdResult = NFD_ERROR;
  724. HRESULT coResult = COMInit();
  725. if (!COMIsInitialized(coResult))
  726. {
  727. NFDi_SetError("Could not initialize COM.");
  728. return nfdResult;
  729. }
  730. // Create dialog
  731. IFileOpenDialog *fileOpenDialog;
  732. HRESULT result = CoCreateInstance(&CLSID_FileOpenDialog, NULL,
  733. CLSCTX_ALL, &IID_IFileOpenDialog,
  734. (void**)(&fileOpenDialog) );
  735. if ( !SUCCEEDED(result) )
  736. {
  737. NFDi_SetError("Could not create dialog.");
  738. goto end;
  739. }
  740. // Build the filter list
  741. if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
  742. {
  743. goto end;
  744. }
  745. // Set the default path
  746. if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
  747. {
  748. goto end;
  749. }
  750. // Show the dialog.
  751. result = fileOpenDialog->lpVtbl->Show(fileOpenDialog, NULL);
  752. if ( SUCCEEDED(result) )
  753. {
  754. // Get the file name
  755. IShellItem *shellItem;
  756. result = fileOpenDialog->lpVtbl->GetResult(fileOpenDialog , & shellItem);
  757. if ( !SUCCEEDED(result) )
  758. {
  759. NFDi_SetError("Could not get shell item from dialog.");
  760. goto end;
  761. }
  762. wchar_t *filePath;
  763. result = shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &filePath);
  764. if ( !SUCCEEDED(result) )
  765. {
  766. NFDi_SetError("Could not get file path for selected.");
  767. shellItem->lpVtbl->Release(shellItem);
  768. goto end;
  769. }
  770. CopyWCharToNFDChar( filePath, outPath );
  771. CoTaskMemFree(filePath);
  772. if ( !*outPath )
  773. {
  774. /* error is malloc-based, error message would be redundant */
  775. shellItem->lpVtbl->Release(shellItem);
  776. goto end;
  777. }
  778. nfdResult = NFD_OKAY;
  779. shellItem->lpVtbl->Release(shellItem);
  780. }
  781. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  782. {
  783. nfdResult = NFD_CANCEL;
  784. }
  785. else
  786. {
  787. NFDi_SetError("File dialog box show failed.");
  788. nfdResult = NFD_ERROR;
  789. }
  790. end:
  791. if (fileOpenDialog)
  792. fileOpenDialog->lpVtbl->Release(fileOpenDialog);
  793. COMUninit(coResult);
  794. return nfdResult;
  795. }
  796. nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
  797. const nfdchar_t *defaultPath,
  798. nfdpathset_t *outPaths )
  799. {
  800. nfdresult_t nfdResult = NFD_ERROR;
  801. HRESULT coResult = COMInit();
  802. if (!COMIsInitialized(coResult))
  803. {
  804. NFDi_SetError("Could not initialize COM.");
  805. return nfdResult;
  806. }
  807. // Create dialog
  808. IFileOpenDialog *fileOpenDialog;
  809. HRESULT result = CoCreateInstance(&CLSID_FileOpenDialog, NULL,
  810. CLSCTX_ALL, &IID_IFileOpenDialog,
  811. (void**)(&fileOpenDialog) );
  812. if ( !SUCCEEDED(result) )
  813. {
  814. fileOpenDialog = NULL;
  815. NFDi_SetError("Could not create dialog.");
  816. goto end;
  817. }
  818. // Build the filter list
  819. if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
  820. {
  821. goto end;
  822. }
  823. // Set the default path
  824. if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
  825. {
  826. goto end;
  827. }
  828. // Set a flag for multiple options
  829. DWORD dwFlags;
  830. result = fileOpenDialog->lpVtbl->GetOptions(fileOpenDialog , & dwFlags);
  831. if ( !SUCCEEDED(result) )
  832. {
  833. NFDi_SetError("Could not get options.");
  834. goto end;
  835. }
  836. result = fileOpenDialog->lpVtbl->SetOptions(fileOpenDialog, dwFlags | FOS_ALLOWMULTISELECT);
  837. if ( !SUCCEEDED(result) )
  838. {
  839. NFDi_SetError("Could not set options.");
  840. goto end;
  841. }
  842. // Show the dialog.
  843. result = fileOpenDialog->lpVtbl->Show(fileOpenDialog, NULL);
  844. if ( SUCCEEDED(result) )
  845. {
  846. IShellItemArray *shellItems;
  847. result = fileOpenDialog->lpVtbl->GetResults(fileOpenDialog, & shellItems);
  848. if ( !SUCCEEDED(result) )
  849. {
  850. NFDi_SetError("Could not get shell items.");
  851. goto end;
  852. }
  853. if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
  854. {
  855. shellItems->lpVtbl->Release(shellItems);
  856. goto end;
  857. }
  858. shellItems->lpVtbl->Release(shellItems);
  859. nfdResult = NFD_OKAY;
  860. }
  861. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  862. {
  863. nfdResult = NFD_CANCEL;
  864. }
  865. else
  866. {
  867. NFDi_SetError("File dialog box show failed.");
  868. nfdResult = NFD_ERROR;
  869. }
  870. end:
  871. if ( fileOpenDialog )
  872. fileOpenDialog->lpVtbl->Release(fileOpenDialog);
  873. COMUninit(coResult);
  874. return nfdResult;
  875. }
  876. nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
  877. const nfdchar_t *defaultPath,
  878. nfdchar_t **outPath )
  879. {
  880. nfdresult_t nfdResult = NFD_ERROR;
  881. HRESULT coResult = COMInit();
  882. if (!COMIsInitialized(coResult))
  883. {
  884. NFDi_SetError("Could not initialize COM.");
  885. return nfdResult;
  886. }
  887. // Create dialog
  888. IFileSaveDialog *fileSaveDialog;
  889. HRESULT result = CoCreateInstance(&CLSID_FileSaveDialog, NULL,
  890. CLSCTX_ALL, &IID_IFileSaveDialog,
  891. (void**)(&fileSaveDialog) );
  892. if ( !SUCCEEDED(result) )
  893. {
  894. fileSaveDialog = NULL;
  895. NFDi_SetError("Could not create dialog.");
  896. goto end;
  897. }
  898. // Build the filter list
  899. if ( !AddFiltersToDialog( fileSaveDialog, filterList ) )
  900. {
  901. goto end;
  902. }
  903. // Set the default path
  904. if ( !SetDefaultPath( fileSaveDialog, defaultPath ) )
  905. {
  906. goto end;
  907. }
  908. // Show the dialog.
  909. result = fileSaveDialog->lpVtbl->Show(fileSaveDialog, NULL);
  910. if ( SUCCEEDED(result) )
  911. {
  912. // Get the file name
  913. IShellItem *shellItem;
  914. result = fileSaveDialog->lpVtbl->GetResult(fileSaveDialog , & shellItem);
  915. if ( !SUCCEEDED(result) )
  916. {
  917. NFDi_SetError("Could not get shell item from dialog.");
  918. goto end;
  919. }
  920. wchar_t *filePath;
  921. result = shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &filePath);
  922. if ( !SUCCEEDED(result) )
  923. {
  924. shellItem->lpVtbl->Release(shellItem);
  925. NFDi_SetError("Could not get file path for selected.");
  926. goto end;
  927. }
  928. CopyWCharToNFDChar( filePath, outPath );
  929. CoTaskMemFree(filePath);
  930. if ( !*outPath )
  931. {
  932. /* error is malloc-based, error message would be redundant */
  933. shellItem->lpVtbl->Release(shellItem);
  934. goto end;
  935. }
  936. nfdResult = NFD_OKAY;
  937. shellItem->lpVtbl->Release(shellItem);
  938. }
  939. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  940. {
  941. nfdResult = NFD_CANCEL;
  942. }
  943. else
  944. {
  945. NFDi_SetError("File dialog box show failed.");
  946. nfdResult = NFD_ERROR;
  947. }
  948. end:
  949. if ( fileSaveDialog )
  950. fileSaveDialog->lpVtbl->Release(fileSaveDialog);
  951. COMUninit(coResult);
  952. return nfdResult;
  953. }
  954. nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
  955. nfdchar_t **outPath)
  956. {
  957. nfdresult_t nfdResult = NFD_ERROR;
  958. DWORD dwOptions = 0;
  959. HRESULT coResult = COMInit();
  960. if (!COMIsInitialized(coResult))
  961. {
  962. NFDi_SetError("CoInitializeEx failed.");
  963. return nfdResult;
  964. }
  965. // Create dialog
  966. IFileOpenDialog *fileDialog;
  967. HRESULT result = CoCreateInstance(&CLSID_FileOpenDialog,
  968. NULL,
  969. CLSCTX_ALL, &IID_IFileOpenDialog, (void **)&fileDialog);
  970. if ( !SUCCEEDED(result) )
  971. {
  972. NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed.");
  973. goto end;
  974. }
  975. // Set the default path
  976. if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY)
  977. {
  978. NFDi_SetError("SetDefaultPath failed.");
  979. goto end;
  980. }
  981. // Get the dialogs options
  982. if (!SUCCEEDED(fileDialog->lpVtbl->GetOptions(fileDialog , & dwOptions)))
  983. {
  984. NFDi_SetError("GetOptions for IFileDialog failed.");
  985. goto end;
  986. }
  987. // Add in FOS_PICKFOLDERS which hides files and only allows selection of folders
  988. if (!SUCCEEDED(fileDialog->lpVtbl->SetOptions(fileDialog, dwOptions | FOS_PICKFOLDERS)))
  989. {
  990. NFDi_SetError("SetOptions for IFileDialog failed.");
  991. goto end;
  992. }
  993. // Show the dialog to the user
  994. result = fileDialog->lpVtbl->Show(fileDialog, NULL);
  995. if ( SUCCEEDED(result) )
  996. {
  997. // Get the folder name
  998. IShellItem *shellItem;
  999. result = fileDialog->lpVtbl->GetResult(fileDialog , & shellItem);
  1000. if ( !SUCCEEDED(result) )
  1001. {
  1002. NFDi_SetError("Could not get file path for selected.");
  1003. shellItem->lpVtbl->Release(shellItem);
  1004. goto end;
  1005. }
  1006. wchar_t *path = NULL;
  1007. result = shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_DESKTOPABSOLUTEPARSING, &path);
  1008. if ( !SUCCEEDED(result) )
  1009. {
  1010. NFDi_SetError("GetDisplayName for IShellItem failed.");
  1011. shellItem->lpVtbl->Release(shellItem);
  1012. goto end;
  1013. }
  1014. CopyWCharToNFDChar(path, outPath);
  1015. CoTaskMemFree(path);
  1016. if ( !*outPath )
  1017. {
  1018. shellItem->lpVtbl->Release(shellItem);
  1019. goto end;
  1020. }
  1021. nfdResult = NFD_OKAY;
  1022. shellItem->lpVtbl->Release(shellItem);
  1023. }
  1024. else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
  1025. {
  1026. nfdResult = NFD_CANCEL;
  1027. }
  1028. else
  1029. {
  1030. NFDi_SetError("Show for IFileDialog failed.");
  1031. nfdResult = NFD_ERROR;
  1032. }
  1033. end:
  1034. if (fileDialog)
  1035. fileDialog->lpVtbl->Release(fileDialog);
  1036. COMUninit(coResult);
  1037. return nfdResult;
  1038. }
  1039. #endif