nfd_gtk.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /*
  2. Native File Dialog
  3. http://www.frogtoss.com/labs
  4. */
  5. #include <stdio.h>
  6. #include <assert.h>
  7. #include <string.h>
  8. #include <gtk/gtk.h>
  9. #include "nfd.h"
  10. #include "nfd_common.h"
  11. const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+";
  12. const char NOPATH_MSG[] = "The selected path is out of memory.";
  13. const char NOMEM_MSG[] = "Out of memory.";
  14. static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
  15. {
  16. const char SEP[] = ", ";
  17. size_t len = strlen(filterName);
  18. if ( len != 0 )
  19. {
  20. strncat( filterName, SEP, bufsize - len - 1 );
  21. len += strlen(SEP);
  22. }
  23. strncat( filterName, typebuf, bufsize - len - 1 );
  24. }
  25. static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList )
  26. {
  27. GtkFileFilter *filter;
  28. char typebuf[NFD_MAX_STRLEN] = {0};
  29. const char *p_filterList = filterList;
  30. char *p_typebuf = typebuf;
  31. char filterName[NFD_MAX_STRLEN] = {0};
  32. if ( !filterList || strlen(filterList) == 0 )
  33. return;
  34. filter = gtk_file_filter_new();
  35. while ( 1 )
  36. {
  37. if ( NFDi_IsFilterSegmentChar(*p_filterList) )
  38. {
  39. char typebufWildcard[NFD_MAX_STRLEN];
  40. /* add another type to the filter */
  41. assert( strlen(typebuf) > 0 );
  42. assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
  43. snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
  44. AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
  45. gtk_file_filter_add_pattern( filter, typebufWildcard );
  46. p_typebuf = typebuf;
  47. memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
  48. }
  49. if ( *p_filterList == ';' || *p_filterList == '\0' )
  50. {
  51. /* end of filter -- add it to the dialog */
  52. gtk_file_filter_set_name( filter, filterName );
  53. gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
  54. filterName[0] = '\0';
  55. if ( *p_filterList == '\0' )
  56. break;
  57. filter = gtk_file_filter_new();
  58. }
  59. if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
  60. {
  61. *p_typebuf = *p_filterList;
  62. p_typebuf++;
  63. }
  64. p_filterList++;
  65. }
  66. /* always append a wildcard option to the end*/
  67. filter = gtk_file_filter_new();
  68. gtk_file_filter_set_name( filter, "*.*" );
  69. gtk_file_filter_add_pattern( filter, "*" );
  70. gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
  71. }
  72. static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath )
  73. {
  74. if ( !defaultPath || strlen(defaultPath) == 0 )
  75. return;
  76. /* GTK+ manual recommends not specifically setting the default path.
  77. We do it anyway in order to be consistent across platforms.
  78. If consistency with the native OS is preferred, this is the line
  79. to comment out. -ml */
  80. gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath );
  81. }
  82. static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet )
  83. {
  84. size_t bufSize = 0;
  85. GSList *node;
  86. nfdchar_t *p_buf;
  87. size_t count = 0;
  88. assert(fileList);
  89. assert(pathSet);
  90. pathSet->count = (size_t)g_slist_length( fileList );
  91. assert( pathSet->count > 0 );
  92. pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
  93. if ( !pathSet->indices )
  94. {
  95. NFDi_SetError(NOMEM_MSG);
  96. return NFD_ERROR;
  97. }
  98. /* count the total space needed for buf */
  99. for ( node = fileList; node; node = node->next )
  100. {
  101. assert(node->data);
  102. bufSize += strlen( (const gchar*)node->data ) + 1;
  103. }
  104. pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
  105. /* fill buf */
  106. p_buf = pathSet->buf;
  107. for ( node = fileList; node; node = node->next )
  108. {
  109. nfdchar_t *path = (nfdchar_t*)(node->data);
  110. size_t byteLen = strlen(path)+1;
  111. ptrdiff_t index;
  112. memcpy( p_buf, path, byteLen );
  113. g_free(node->data);
  114. index = p_buf - pathSet->buf;
  115. assert( index >= 0 );
  116. pathSet->indices[count] = (size_t)index;
  117. p_buf += byteLen;
  118. ++count;
  119. }
  120. g_slist_free( fileList );
  121. return NFD_OKAY;
  122. }
  123. static void WaitForCleanup(void)
  124. {
  125. while (gtk_events_pending())
  126. gtk_main_iteration();
  127. }
  128. /* public */
  129. nfdresult_t NFD_OpenDialog( const char *filterList,
  130. const nfdchar_t *defaultPath,
  131. nfdchar_t **outPath )
  132. {
  133. GtkWidget *dialog;
  134. nfdresult_t result;
  135. if ( !gtk_init_check( NULL, NULL ) )
  136. {
  137. NFDi_SetError(INIT_FAIL_MSG);
  138. return NFD_ERROR;
  139. }
  140. dialog = gtk_file_chooser_dialog_new( "Open File",
  141. NULL,
  142. GTK_FILE_CHOOSER_ACTION_OPEN,
  143. "_Cancel", GTK_RESPONSE_CANCEL,
  144. "_Open", GTK_RESPONSE_ACCEPT,
  145. NULL );
  146. /* Build the filter list */
  147. AddFiltersToDialog(dialog, filterList);
  148. /* Set the default path */
  149. SetDefaultPath(dialog, defaultPath);
  150. result = NFD_CANCEL;
  151. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  152. {
  153. char *filename;
  154. filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
  155. {
  156. size_t len = strlen(filename);
  157. *outPath = NFDi_Malloc( len + 1 );
  158. memcpy( *outPath, filename, len + 1 );
  159. if ( !*outPath )
  160. {
  161. g_free( filename );
  162. gtk_widget_destroy(dialog);
  163. NFDi_SetError(NOPATH_MSG);
  164. return NFD_ERROR;
  165. }
  166. }
  167. g_free( filename );
  168. result = NFD_OKAY;
  169. }
  170. gtk_widget_destroy(dialog);
  171. WaitForCleanup();
  172. return result;
  173. }
  174. nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
  175. const nfdchar_t *defaultPath,
  176. nfdpathset_t *outPaths )
  177. {
  178. GtkWidget *dialog;
  179. nfdresult_t result;
  180. if ( !gtk_init_check( NULL, NULL ) )
  181. {
  182. NFDi_SetError(INIT_FAIL_MSG);
  183. return NFD_ERROR;
  184. }
  185. dialog = gtk_file_chooser_dialog_new( "Open Files",
  186. NULL,
  187. GTK_FILE_CHOOSER_ACTION_OPEN,
  188. "_Cancel", GTK_RESPONSE_CANCEL,
  189. "_Open", GTK_RESPONSE_ACCEPT,
  190. NULL );
  191. gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE );
  192. /* Build the filter list */
  193. AddFiltersToDialog(dialog, filterList);
  194. /* Set the default path */
  195. SetDefaultPath(dialog, defaultPath);
  196. result = NFD_CANCEL;
  197. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  198. {
  199. GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
  200. if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR )
  201. {
  202. gtk_widget_destroy(dialog);
  203. NFDi_SetError(NOPATH_MSG);
  204. return NFD_ERROR;
  205. }
  206. result = NFD_OKAY;
  207. }
  208. gtk_widget_destroy(dialog);
  209. WaitForCleanup();
  210. return result;
  211. }
  212. nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
  213. const nfdchar_t *defaultPath,
  214. const nfdchar_t *defaultFilename,
  215. nfdchar_t **outPath )
  216. {
  217. GtkWidget *dialog;
  218. nfdresult_t result;
  219. if ( !gtk_init_check( NULL, NULL ) )
  220. {
  221. NFDi_SetError(INIT_FAIL_MSG);
  222. return NFD_ERROR;
  223. }
  224. dialog = gtk_file_chooser_dialog_new( "Save File",
  225. NULL,
  226. GTK_FILE_CHOOSER_ACTION_SAVE,
  227. "_Cancel", GTK_RESPONSE_CANCEL,
  228. "_Save", GTK_RESPONSE_ACCEPT,
  229. NULL );
  230. gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
  231. /* Build the filter list */
  232. AddFiltersToDialog(dialog, filterList);
  233. SetDefaultPath(dialog, defaultPath);
  234. result = NFD_CANCEL;
  235. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  236. {
  237. char *filename;
  238. filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
  239. {
  240. size_t len = strlen(filename);
  241. *outPath = NFDi_Malloc( len + 1 );
  242. memcpy( *outPath, filename, len + 1 );
  243. if ( !*outPath )
  244. {
  245. g_free( filename );
  246. gtk_widget_destroy(dialog);
  247. NFDi_SetError(NOPATH_MSG);
  248. return NFD_ERROR;
  249. }
  250. }
  251. g_free(filename);
  252. result = NFD_OKAY;
  253. }
  254. gtk_widget_destroy(dialog);
  255. WaitForCleanup();
  256. return result;
  257. }
  258. nfdresult_t NFD_ChooseDirectory( const nfdchar_t *prompt,
  259. const nfdchar_t *defaultPath,
  260. nfdchar_t **outPath )
  261. {
  262. GtkWidget *dialog = NULL;
  263. nfdresult_t result = NFD_ERROR;
  264. if ( !gtk_init_check( NULL, NULL ) )
  265. {
  266. NFDi_SetError(INIT_FAIL_MSG);
  267. return NFD_ERROR;
  268. }
  269. dialog = gtk_file_chooser_dialog_new( prompt,
  270. NULL,
  271. GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
  272. "_Cancel", GTK_RESPONSE_CANCEL,
  273. "_Open", GTK_RESPONSE_ACCEPT,
  274. NULL );
  275. SetDefaultPath(dialog, defaultPath);
  276. result = NFD_CANCEL;
  277. if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
  278. {
  279. char *filename = NULL;
  280. filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
  281. {
  282. size_t len = strlen(filename);
  283. *outPath = NFDi_Malloc( len + 1 );
  284. memcpy( *outPath, filename, len + 1 );
  285. if ( !*outPath )
  286. {
  287. g_free( filename );
  288. gtk_widget_destroy(dialog);
  289. NFDi_SetError(NOPATH_MSG);
  290. return NFD_ERROR;
  291. }
  292. }
  293. g_free(filename);
  294. result = NFD_OKAY;
  295. }
  296. gtk_widget_destroy(dialog);
  297. WaitForCleanup();
  298. return result;
  299. }