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