1 /*
2 * TilEm II
3 *
4 * Copyright (c) 2011 Benjamin Moody
5 * Copyright (c) 2011 Thibault Duponchelle // FIXME : My work is based on yours benjamin. Should I put "portions"?! Or something else ?
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <stdarg.h>
27 #include <string.h>
28 #include <gtk/gtk.h>
29
30 #include "gtk-compat.h"
31 #include "filedlg.h"
32
33 #ifdef GDK_WINDOWING_WIN32
34 # define WIN32_LEAN_AND_MEAN
35 # include <windows.h>
36 # include <commdlg.h>
37 # include <shlobj.h>
38 # include <gdk/gdkwin32.h>
39 # include <wchar.h>
40
41 # ifndef OPENFILENAME_SIZE_VERSION_400
42 # define OPENFILENAME_SIZE_VERSION_400 sizeof(OPENFILENAMEA)
43 # endif
44
45 struct fcinfo {
46 const char *title;
47 gboolean save;
48 HWND parent_window;
49 char *filename;
50 char *dirname;
51 char *extension;
52 const char *filters;
53 unsigned int flags;
54 };
55
56 #define BUFFER_SIZE 32768
57
file_chooser_main(const struct fcinfo * fci)58 static char * file_chooser_main(const struct fcinfo *fci)
59 {
60 if (G_WIN32_HAVE_WIDECHAR_API()) {
61 OPENFILENAMEW ofnw;
62 wchar_t *titlew, *filterw, *initdirw, *defextw;
63 wchar_t filenamew[BUFFER_SIZE + 1];
64 wchar_t *p;
65 int result;
66 int i;
67
68 titlew = g_utf8_to_utf16(fci->title, -1, 0, 0, 0);
69
70 filterw = g_utf8_to_utf16(fci->filters, -1, 0, 0, 0);
71 for (i = 0; filterw[i]; i++)
72 if (filterw[i] == '\n') filterw[i] = 0;
73
74 memset(&ofnw, 0, sizeof(ofnw));
75 ofnw.lStructSize = OPENFILENAME_SIZE_VERSION_400;
76
77 ofnw.hwndOwner = fci->parent_window;
78 ofnw.lpstrTitle = titlew;
79 ofnw.lpstrFilter = filterw;
80 ofnw.nFilterIndex = 1;
81 ofnw.lpstrFile = filenamew;
82 ofnw.nMaxFile = BUFFER_SIZE;
83
84 memset(filenamew, 0, sizeof(filenamew));
85
86 if (fci->filename) {
87 p = g_utf8_to_utf16(fci->filename, -1, 0, 0, 0);
88 if (p) {
89 wcsncpy(filenamew, p, BUFFER_SIZE);
90 g_free(p);
91 }
92 }
93
94 if (fci->dirname)
95 initdirw = g_utf8_to_utf16(fci->dirname, -1, 0, 0, 0);
96 else
97 initdirw = NULL;
98
99 if (fci->extension)
100 defextw = g_utf8_to_utf16(fci->extension, -1, 0, 0, 0);
101 else
102 defextw = NULL;
103
104 ofnw.lpstrInitialDir = initdirw;
105 ofnw.lpstrDefExt = defextw;
106
107 ofnw.Flags = fci->flags;
108
109 result = (fci->save
110 ? GetSaveFileNameW(&ofnw)
111 : GetOpenFileNameW(&ofnw));
112
113 g_free(titlew);
114 g_free(filterw);
115 g_free(initdirw);
116 g_free(defextw);
117
118 if (!result)
119 return NULL;
120
121 if ((fci->flags & OFN_ALLOWMULTISELECT)) {
122 for (i = 0; i < BUFFER_SIZE; i++) {
123 if (filenamew[i] == 0 && filenamew[i + 1] == 0)
124 break;
125 else if (filenamew[i] == '/')
126 filenamew[i] = '\\';
127 else if (filenamew[i] == 0)
128 filenamew[i] = '/';
129 }
130 }
131
132 return g_utf16_to_utf8(filenamew, -1, 0, 0, 0);
133 }
134 else {
135 OPENFILENAMEA ofna;
136 char *titlel, *filterl, *initdirl, *defextl;
137 char filenamel[BUFFER_SIZE + 1];
138 char *p;
139 int result;
140 int i;
141
142 titlel = g_locale_from_utf8(fci->title, -1, 0, 0, 0);
143
144 filterl = g_locale_from_utf8(fci->filters, -1, 0, 0, 0);
145 for (i = 0; filterl[i]; i++)
146 if (filterl[i] == '\n') filterl[i] = 0;
147
148 memset(&ofna, 0, sizeof(ofna));
149 ofna.lStructSize = OPENFILENAME_SIZE_VERSION_400;
150
151 ofna.hwndOwner = fci->parent_window;
152 ofna.lpstrTitle = titlel;
153 ofna.lpstrFilter = filterl;
154 ofna.nFilterIndex = 1;
155 ofna.lpstrFile = filenamel;
156 ofna.nMaxFile = BUFFER_SIZE;
157
158 memset(filenamel, 0, sizeof(filenamel));
159
160 if (fci->filename) {
161 p = g_locale_from_utf8(fci->filename, -1, 0, 0, 0);
162 if (p) {
163 strncpy(filenamel, p, BUFFER_SIZE);
164 g_free(p);
165 }
166 }
167
168 if (fci->dirname)
169 initdirl = g_locale_from_utf8(fci->dirname, -1, 0, 0, 0);
170 else
171 initdirl = NULL;
172
173 if (fci->extension)
174 defextl = g_locale_from_utf8(fci->extension, -1, 0, 0, 0);
175 else
176 defextl = NULL;
177
178 ofna.lpstrInitialDir = initdirl;
179 ofna.lpstrDefExt = defextl;
180
181 ofna.Flags = fci->flags;
182
183 result = (fci->save
184 ? GetSaveFileNameA(&ofna)
185 : GetOpenFileNameA(&ofna));
186
187 g_free(titlel);
188 g_free(filterl);
189 g_free(initdirl);
190 g_free(defextl);
191
192 if (!result)
193 return NULL;
194
195 if ((fci->flags & OFN_ALLOWMULTISELECT)) {
196 for (i = 0; i < BUFFER_SIZE; i++) {
197 if (filenamel[i] == 0 && filenamel[i + 1] == 0)
198 break;
199 else if (filenamel[i] == '/')
200 filenamel[i] = '\\';
201 else if (filenamel[i] == 0)
202 filenamel[i] = '/';
203 }
204 }
205
206 return g_locale_to_utf8(filenamel, -1, 0, 0, 0);
207 }
208 }
209
wakeup(G_GNUC_UNUSED gpointer data)210 static gboolean wakeup(G_GNUC_UNUSED gpointer data)
211 {
212 gtk_main_quit();
213 return FALSE;
214 }
215
file_chooser_thread(gpointer data)216 static gpointer file_chooser_thread(gpointer data)
217 {
218 struct fcinfo *fci = data;
219 gpointer res = file_chooser_main(fci);
220 g_idle_add(wakeup, NULL);
221 return res;
222 }
223
build_filter_string(const char * desc1,const char * pattern1,va_list ap)224 static char * build_filter_string(const char *desc1,
225 const char *pattern1,
226 va_list ap)
227 {
228 GString *str = g_string_new(NULL);
229
230 while (desc1 && pattern1) {
231 if (pattern1[0]) {
232 g_string_append(str, desc1);
233 g_string_append_c(str, '\n');
234 g_string_append(str, pattern1);
235 g_string_append_c(str, '\n');
236 }
237
238 desc1 = va_arg(ap, char *);
239 if (!desc1) break;
240 pattern1 = va_arg(ap, char *);
241 }
242
243 return g_string_free(str, FALSE);
244 }
245
run_file_chooser1(const char * title,GtkWindow * parent,gboolean save,gboolean multiple,const char * suggest_name,const char * suggest_dir,const char * filters)246 static char ** run_file_chooser1(const char *title,
247 GtkWindow *parent,
248 gboolean save,
249 gboolean multiple,
250 const char *suggest_name,
251 const char *suggest_dir,
252 const char *filters)
253 {
254 struct fcinfo fci;
255 GThread *thread;
256 GtkWidget *dummy;
257 GdkWindow *pwin;
258 char *fname, *p, *dir;
259 char **result;
260 int i;
261
262 if (!g_thread_supported())
263 g_thread_init(NULL);
264
265 fci.title = title;
266 fci.save = save;
267
268 if (parent && (pwin = gtk_widget_get_window(GTK_WIDGET(parent))))
269 fci.parent_window = GDK_WINDOW_HWND(pwin);
270 else
271 fci.parent_window = NULL;
272
273 if (suggest_name && suggest_dir) {
274 fci.filename = g_build_filename(suggest_dir,
275 suggest_name, NULL);
276 fci.dirname = NULL;
277 }
278 else if (suggest_name) {
279 fci.filename = g_strdup(suggest_name);
280 fci.dirname = NULL;
281 }
282 else if (suggest_dir) {
283 fci.filename = NULL;
284 fci.dirname = g_strdup(suggest_dir);
285 }
286 else {
287 fci.filename = fci.dirname = NULL;
288 }
289
290 if (suggest_name && (p = strrchr(suggest_name, '.')))
291 fci.extension = g_strdup(p + 1);
292 else
293 fci.extension = NULL;
294
295 fci.filters = filters;
296
297 fci.flags = (OFN_HIDEREADONLY | OFN_EXPLORER);
298
299 if (save)
300 fci.flags |= OFN_OVERWRITEPROMPT;
301 else {
302 fci.flags |= OFN_FILEMUSTEXIST;
303 if (multiple)
304 fci.flags |= OFN_ALLOWMULTISELECT;
305 }
306
307 if ((thread = g_thread_create(file_chooser_thread, &fci, TRUE, NULL))) {
308 dummy = gtk_invisible_new();
309 gtk_grab_add(dummy);
310 gtk_main();
311 fname = g_thread_join(thread);
312 gtk_widget_destroy(dummy);
313 }
314 else {
315 fname = file_chooser_main(&fci);
316 }
317
318 g_free(fci.filename);
319 g_free(fci.dirname);
320 g_free(fci.extension);
321
322 if (!fname) {
323 return NULL;
324 }
325 else if (multiple && (p = strchr(fname, '/'))) {
326 dir = g_strndup(fname, p - fname);
327 result = g_strsplit(p + 1, "/", -1);
328
329 for (i = 0; result[i]; i++) {
330 p = result[i];
331 result[i] = g_build_filename(dir, p, NULL);
332 g_free(p);
333 }
334
335 g_free(fname);
336 return result;
337 }
338 else {
339 result = g_new(char *, 2);
340 result[0] = fname;
341 result[1] = NULL;
342 return result;
343 }
344 }
345
run_file_chooser(const char * title,GtkWindow * parent,gboolean save,gboolean multiple,const char * suggest_name,const char * suggest_dir,const char * desc1,const char * pattern1,va_list ap)346 static char ** run_file_chooser(const char *title,
347 GtkWindow *parent,
348 gboolean save,
349 gboolean multiple,
350 const char *suggest_name,
351 const char *suggest_dir,
352 const char *desc1,
353 const char *pattern1,
354 va_list ap)
355 {
356 char *filters;
357 char **result;
358 filters = build_filter_string(desc1, pattern1, ap);
359 result = run_file_chooser1(title, parent, save, multiple,
360 suggest_name, suggest_dir, filters);
361 g_free(filters);
362 return result;
363 }
364
365 struct dcinfo {
366 const char *title;
367 HWND parent_window;
368 wchar_t *suggest_dir_w;
369 char *suggest_dir_l;
370 };
371
dir_chooser_callback(HWND hwnd,UINT uMsg,G_GNUC_UNUSED LPARAM lParam,LPARAM lpData)372 static int CALLBACK dir_chooser_callback(HWND hwnd, UINT uMsg,
373 G_GNUC_UNUSED LPARAM lParam,
374 LPARAM lpData)
375 {
376 const struct dcinfo *dci = (struct dcinfo*) lpData;
377
378 if (uMsg != BFFM_INITIALIZED)
379 return 0;
380
381 if (G_WIN32_HAVE_WIDECHAR_API())
382 SendMessageW(hwnd, BFFM_SETSELECTIONW,
383 TRUE, (LPARAM) dci->suggest_dir_w);
384 else
385 SendMessageA(hwnd, BFFM_SETSELECTIONA,
386 TRUE, (LPARAM) dci->suggest_dir_l);
387 return 0;
388 }
389
dir_chooser_main(const struct dcinfo * dci)390 static char * dir_chooser_main(const struct dcinfo *dci)
391 {
392 LPITEMIDLIST idl;
393 char *result = NULL;
394
395 CoInitialize(NULL);
396
397 if (G_WIN32_HAVE_WIDECHAR_API()) {
398 BROWSEINFOW bifw;
399 wchar_t dirnamew[MAX_PATH + 1];
400
401 memset(&bifw, 0, sizeof(bifw));
402 bifw.hwndOwner = dci->parent_window;
403 bifw.lpszTitle = g_utf8_to_utf16(dci->title, -1, 0, 0, 0);
404 bifw.ulFlags = (BIF_RETURNONLYFSDIRS | BIF_USENEWUI);
405 bifw.lpfn = &dir_chooser_callback;
406 bifw.lParam = (LPARAM) dci;
407
408 idl = SHBrowseForFolderW(&bifw);
409 if (idl && SHGetPathFromIDListW(idl, dirnamew))
410 result = g_utf16_to_utf8(dirnamew, -1, 0, 0, 0);
411 }
412 else {
413 BROWSEINFOA bifa;
414 char dirnamel[MAX_PATH + 1];
415
416 memset(&bifa, 0, sizeof(bifa));
417 bifa.hwndOwner = dci->parent_window;
418 bifa.lpszTitle = g_locale_from_utf8(dci->title, -1, 0, 0, 0);
419 bifa.ulFlags = (BIF_RETURNONLYFSDIRS | BIF_USENEWUI);
420 bifa.lpfn = &dir_chooser_callback;
421 bifa.lParam = (LPARAM) dci;
422
423 idl = SHBrowseForFolderA(&bifa);
424 if (idl && SHGetPathFromIDListA(idl, dirnamel))
425 result = g_locale_to_utf8(dirnamel, -1, 0, 0, 0);
426 }
427
428 if (idl)
429 CoTaskMemFree(idl);
430
431 CoUninitialize();
432
433 return result;
434 }
435
dir_chooser_thread(gpointer data)436 static gpointer dir_chooser_thread(gpointer data)
437 {
438 struct dcinfo *dci = data;
439 gpointer res = dir_chooser_main(dci);
440 g_idle_add(wakeup, NULL);
441 return res;
442 }
443
run_dir_chooser(G_GNUC_UNUSED const char * title,GtkWindow * parent,G_GNUC_UNUSED gboolean save,const char * suggest_dir)444 static char* run_dir_chooser(G_GNUC_UNUSED const char *title,
445 GtkWindow *parent,
446 G_GNUC_UNUSED gboolean save,
447 const char *suggest_dir)
448 {
449 struct dcinfo dci;
450 GdkWindow *pwin;
451 GThread *thread;
452 GtkWidget *dummy;
453 char *dname;
454
455 if (!g_thread_supported())
456 g_thread_init(NULL);
457
458 dci.title = "Select a folder to save received files.";
459
460 if (parent && (pwin = gtk_widget_get_window(GTK_WIDGET(parent))))
461 dci.parent_window = GDK_WINDOW_HWND(pwin);
462 else
463 dci.parent_window = NULL;
464
465 if (suggest_dir) {
466 dci.suggest_dir_w = g_utf8_to_utf16(suggest_dir, -1, 0, 0, 0);
467 dci.suggest_dir_l = g_locale_from_utf8(suggest_dir, -1, 0, 0, 0);
468 }
469 else {
470 dci.suggest_dir_w = NULL;
471 dci.suggest_dir_l = NULL;
472 }
473
474 if ((thread = g_thread_create(dir_chooser_thread, &dci, TRUE, NULL))) {
475 dummy = gtk_invisible_new();
476 gtk_grab_add(dummy);
477 gtk_main();
478 dname = g_thread_join(thread);
479 gtk_widget_destroy(dummy);
480 }
481 else {
482 dname = dir_chooser_main(&dci);
483 }
484
485 g_free(dci.suggest_dir_w);
486 g_free(dci.suggest_dir_l);
487
488 return dname;
489 }
490
491 #else /* ! GDK_WINDOWING_WIN32 */
492
493 /* Case insensitive filter function */
filter_lowercase(const GtkFileFilterInfo * info,gpointer data)494 static gboolean filter_lowercase(const GtkFileFilterInfo *info,
495 gpointer data)
496 {
497 GSList *list = data;
498 const char *base;
499 char *lowercase, *reversed;
500 int length;
501 gboolean matched = FALSE;
502
503 if ((base = strrchr(info->filename, G_DIR_SEPARATOR)))
504 base++;
505 else
506 base = info->filename;
507
508 lowercase = g_ascii_strdown(base, -1);
509 length = strlen(lowercase);
510 reversed = g_memdup(lowercase, length + 1);
511 g_strreverse(reversed);
512
513 while (list) {
514 if (g_pattern_match(list->data, length,
515 lowercase, reversed)) {
516 matched = TRUE;
517 break;
518 }
519 list = list->next;
520 }
521
522 g_free(lowercase);
523 g_free(reversed);
524 return matched;
525 }
526
free_filter_info(gpointer data)527 static void free_filter_info(gpointer data)
528 {
529 GSList *list = data, *l;
530 for (l = list; l; l = l->next)
531 g_pattern_spec_free(l->data);
532 g_slist_free(list);
533 }
534
setup_file_filters(GtkFileChooser * chooser,const char * desc1,const char * pattern1,va_list ap)535 static void setup_file_filters(GtkFileChooser *chooser,
536 const char *desc1,
537 const char *pattern1,
538 va_list ap)
539 {
540 GtkFileFilter *ffilt;
541 char **pats;
542 GPatternSpec *pspec;
543 GSList *pspeclist;
544 int i;
545
546 while (desc1 && pattern1) {
547 if (pattern1[0]) {
548 ffilt = gtk_file_filter_new();
549 gtk_file_filter_set_name(ffilt, desc1);
550
551 pats = g_strsplit(pattern1, ";", -1);
552 pspeclist = NULL;
553 for (i = 0; pats && pats[i]; i++) {
554 pspec = g_pattern_spec_new(pats[i]);
555 pspeclist = g_slist_prepend(pspeclist, pspec);
556 }
557 g_strfreev(pats);
558
559 gtk_file_filter_add_custom(ffilt, GTK_FILE_FILTER_FILENAME,
560 &filter_lowercase,
561 pspeclist,
562 &free_filter_info);
563
564 gtk_file_chooser_add_filter(chooser, ffilt);
565 }
566
567 desc1 = va_arg(ap, char *);
568 if (!desc1) break;
569 pattern1 = va_arg(ap, char *);
570 }
571 }
572
prompt_overwrite(const char * fname,GtkWindow * parent)573 static gboolean prompt_overwrite(const char *fname,
574 GtkWindow *parent)
575 {
576 GtkWidget *dlg;
577 GtkWidget *button;
578 char *p, *q;
579
580 if (!g_file_test(fname, G_FILE_TEST_EXISTS))
581 return TRUE;
582
583 if (!g_file_test(fname, G_FILE_TEST_IS_REGULAR))
584 return FALSE;
585
586 p = g_filename_display_basename(fname);
587 dlg = gtk_message_dialog_new(parent,
588 GTK_DIALOG_MODAL,
589 GTK_MESSAGE_QUESTION,
590 GTK_BUTTONS_NONE,
591 "A file named \"%s\" already exists. "
592 "Do you want to replace it?",
593 p);
594 g_free(p);
595
596 p = g_path_get_dirname(fname);
597 q = g_filename_display_basename(p);
598 gtk_message_dialog_format_secondary_markup
599 (GTK_MESSAGE_DIALOG(dlg),
600 "The file already exists in \"%s\". Replacing it will "
601 "overwrite its contents.", q);
602 g_free(p);
603 g_free(q);
604
605 gtk_dialog_add_button(GTK_DIALOG(dlg),
606 GTK_STOCK_CANCEL,
607 GTK_RESPONSE_CANCEL);
608
609 button = gtk_button_new_with_mnemonic("_Replace");
610 gtk_widget_set_can_default(button, TRUE);
611 gtk_button_set_image(GTK_BUTTON(button),
612 gtk_image_new_from_stock(GTK_STOCK_SAVE,
613 GTK_ICON_SIZE_BUTTON));
614 gtk_widget_show(button);
615 gtk_dialog_add_action_widget(GTK_DIALOG(dlg), button,
616 GTK_RESPONSE_ACCEPT);
617
618 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
619 GTK_RESPONSE_ACCEPT,
620 GTK_RESPONSE_CANCEL,
621 -1);
622
623 if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT) {
624 gtk_widget_destroy(dlg);
625 return TRUE;
626 }
627
628 gtk_widget_destroy(dlg);
629 return FALSE;
630 }
631
run_file_chooser(const char * title,GtkWindow * parent,gboolean save,gboolean multiple,const char * suggest_name,const char * suggest_dir,const char * desc1,const char * pattern1,va_list ap)632 static char ** run_file_chooser(const char *title,
633 GtkWindow *parent,
634 gboolean save,
635 gboolean multiple,
636 const char *suggest_name,
637 const char *suggest_dir,
638 const char *desc1,
639 const char *pattern1,
640 va_list ap)
641 {
642 GtkWidget *filesel;
643 GSList *filelist, *l;
644 char *fname;
645 char **fnames;
646 int i, n;
647
648 filesel = gtk_file_chooser_dialog_new(title, parent,
649 (save
650 ? GTK_FILE_CHOOSER_ACTION_SAVE
651 : GTK_FILE_CHOOSER_ACTION_OPEN),
652 GTK_STOCK_CANCEL,
653 GTK_RESPONSE_CANCEL,
654 (save
655 ? GTK_STOCK_SAVE
656 : GTK_STOCK_OPEN),
657 GTK_RESPONSE_ACCEPT,
658 NULL);
659
660 gtk_dialog_set_alternative_button_order(GTK_DIALOG(filesel),
661 GTK_RESPONSE_ACCEPT,
662 GTK_RESPONSE_CANCEL,
663 -1);
664
665 gtk_dialog_set_default_response(GTK_DIALOG(filesel),
666 GTK_RESPONSE_ACCEPT);
667
668 if (suggest_dir)
669 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filesel),
670 suggest_dir);
671
672 if (suggest_name)
673 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filesel),
674 suggest_name);
675
676 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filesel),
677 multiple);
678
679 setup_file_filters(GTK_FILE_CHOOSER(filesel), desc1, pattern1, ap);
680
681 while (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
682 if (save) {
683 fname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
684 if (!fname || !prompt_overwrite(fname, GTK_WINDOW(filesel))) {
685 g_free(fname);
686 continue;
687 }
688
689 fnames = g_new(char *, 2);
690 fnames[0] = fname;
691 fnames[1] = NULL;
692
693 gtk_widget_destroy(filesel);
694 return fnames;
695 }
696 else {
697 filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(filesel));
698 if (!filelist)
699 continue;
700
701 n = g_slist_length(filelist);
702 fnames = g_new(char *, n + 1);
703 i = 0;
704 for (l = filelist; l; l = l->next)
705 fnames[i++] = l->data;
706 g_slist_free(filelist);
707 fnames[n] = NULL;
708
709 for (i = 0; i < n; i++)
710 if (!g_file_test(fnames[i],
711 G_FILE_TEST_IS_REGULAR))
712 break;
713 if (i < n) {
714 g_strfreev(fnames);
715 continue;
716 }
717
718 gtk_widget_destroy(filesel);
719 return fnames;
720 }
721 }
722
723 gtk_widget_destroy(filesel);
724 return NULL;
725 }
726
run_dir_chooser(const char * title,GtkWindow * parent,gboolean save,const char * suggest_dir)727 static char* run_dir_chooser(const char *title,
728 GtkWindow *parent,
729 gboolean save,
730 const char *suggest_dir)
731 {
732 GtkWidget *filesel;
733 char *fname;
734
735 filesel = gtk_file_chooser_dialog_new(title, parent,
736 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
737 GTK_STOCK_CANCEL,
738 GTK_RESPONSE_CANCEL,
739 (save
740 ? GTK_STOCK_SAVE
741 : GTK_STOCK_OPEN),
742 GTK_RESPONSE_ACCEPT,
743 NULL);
744
745 gtk_dialog_set_alternative_button_order(GTK_DIALOG(filesel),
746 GTK_RESPONSE_ACCEPT,
747 GTK_RESPONSE_CANCEL,
748 -1);
749
750 gtk_dialog_set_default_response(GTK_DIALOG(filesel),
751 GTK_RESPONSE_ACCEPT);
752
753 if (suggest_dir)
754 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filesel),
755 suggest_dir);
756
757 while (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
758 fname = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(filesel));
759 if (!fname) {
760 g_free(fname);
761 continue;
762 }
763
764 gtk_widget_destroy(filesel);
765 return fname;
766 }
767
768 gtk_widget_destroy(filesel);
769 return NULL;
770 }
771
772 #endif /* ! GDK_WINDOWING_WIN32 */
773
prompt_open_file(const char * title,GtkWindow * parent,const char * suggest_dir,const char * desc1,const char * pattern1,...)774 char * prompt_open_file(const char *title,
775 GtkWindow *parent,
776 const char *suggest_dir,
777 const char *desc1,
778 const char *pattern1,
779 ...)
780 {
781 char **result, *fname;
782 va_list ap;
783
784 va_start(ap, pattern1);
785 result = run_file_chooser(title, parent, FALSE, FALSE,
786 NULL, suggest_dir,
787 desc1, pattern1, ap);
788 va_end(ap);
789
790 if (!result || !result[0] || result[1]) {
791 g_strfreev(result);
792 return NULL;
793 }
794 else {
795 fname = result[0];
796 g_free(result);
797 return fname;
798 }
799 }
800
prompt_open_files(const char * title,GtkWindow * parent,const char * suggest_dir,const char * desc1,const char * pattern1,...)801 char ** prompt_open_files(const char *title,
802 GtkWindow *parent,
803 const char *suggest_dir,
804 const char *desc1,
805 const char *pattern1,
806 ...)
807 {
808 char **result;
809 va_list ap;
810
811 va_start(ap, pattern1);
812 result = run_file_chooser(title, parent, FALSE, TRUE,
813 NULL, suggest_dir,
814 desc1, pattern1, ap);
815 va_end(ap);
816 return result;
817 }
818
prompt_save_file(const char * title,GtkWindow * parent,const char * suggest_name,const char * suggest_dir,const char * desc1,const char * pattern1,...)819 char * prompt_save_file(const char *title,
820 GtkWindow *parent,
821 const char *suggest_name,
822 const char *suggest_dir,
823 const char *desc1,
824 const char *pattern1,
825 ...)
826 {
827 char **result, *fname;
828 va_list ap;
829
830 va_start(ap, pattern1);
831 result = run_file_chooser(title, parent, TRUE, FALSE,
832 suggest_name, suggest_dir,
833 desc1, pattern1, ap);
834 va_end(ap);
835
836 if (!result || !result[0] || result[1]) {
837 g_strfreev(result);
838 return NULL;
839 }
840 else {
841 fname = result[0];
842 g_free(result);
843 return fname;
844 }
845 }
846
prompt_select_dir(const char * title,GtkWindow * parent,const char * suggest_dir)847 char * prompt_select_dir(const char *title, GtkWindow *parent, const char *suggest_dir)
848 {
849 char *dirname;
850
851 dirname = run_dir_chooser(title, parent, TRUE, suggest_dir);
852
853 if (!dirname) {
854 return NULL;
855 } else {
856 return dirname;
857 }
858 }
859
860
861
862 /**************** File entry ****************/
863
864 #ifdef GDK_WINDOWING_WIN32
865
866 typedef struct _FileEntry {
867 GtkHBox parent;
868 GtkWidget *entry;
869 GtkWidget *button;
870 char *title;
871 char *filters;
872 char *filename;
873 } FileEntry;
874
875 typedef struct _FileEntryClass {
876 GtkHBoxClass parent;
877 } FileEntryClass;
878
879 static guint selection_changed_signal = 0;
880
881 G_DEFINE_TYPE(FileEntry, file_entry, GTK_TYPE_HBOX);
882
file_entry_finalize(GObject * obj)883 static void file_entry_finalize(GObject *obj)
884 {
885 FileEntry *fe = (FileEntry*) obj;
886 g_free(fe->title);
887 g_free(fe->filters);
888 g_free(fe->filename);
889 }
890
file_entry_set_filename(GtkWidget * entry,const char * filename)891 void file_entry_set_filename(GtkWidget *entry,
892 const char *filename)
893 {
894 FileEntry *fe = (FileEntry*) entry;
895
896 if (filename && filename[0]) {
897 if (!fe->filename || strcmp(filename, fe->filename)) {
898 g_free(fe->filename);
899 fe->filename = g_strdup(filename);
900 gtk_entry_set_text(GTK_ENTRY(fe->entry), filename);
901 g_signal_emit(fe, selection_changed_signal, 0, NULL);
902 }
903 }
904 else if (fe->filename) {
905 g_free(fe->filename);
906 fe->filename = NULL;
907 g_signal_emit(fe, selection_changed_signal, 0, NULL);
908 }
909 }
910
file_entry_get_filename(GtkWidget * entry)911 char * file_entry_get_filename(GtkWidget *entry)
912 {
913 FileEntry *fe = (FileEntry*) entry;
914 if (fe->filename)
915 return g_strdup(fe->filename);
916 else
917 return NULL;
918 }
919
focus_changed(G_GNUC_UNUSED GObject * obj,G_GNUC_UNUSED GParamSpec * pspec,gpointer data)920 static void focus_changed(G_GNUC_UNUSED GObject *obj,
921 G_GNUC_UNUSED GParamSpec *pspec,
922 gpointer data)
923 {
924 FileEntry *fe = data;
925 const char *text;
926 text = gtk_entry_get_text(GTK_ENTRY(fe->entry));
927 file_entry_set_filename(GTK_WIDGET(fe), text);
928 }
929
browse_for_files(G_GNUC_UNUSED GtkButton * btn,gpointer data)930 static void browse_for_files(G_GNUC_UNUSED GtkButton *btn, gpointer data)
931 {
932 FileEntry *fe = data;
933 GtkWidget *parent;
934 char **result;
935 char *bname, *dname;
936
937 parent = gtk_widget_get_toplevel(GTK_WIDGET(fe));
938
939 if (fe->filename) {
940 bname = g_path_get_basename(fe->filename);
941 dname = g_path_get_dirname(fe->filename);
942 }
943 else {
944 bname = dname = NULL;
945 }
946
947 result = run_file_chooser1(fe->title, GTK_WINDOW(parent), FALSE, FALSE,
948 bname, dname, fe->filters);
949 g_free(bname);
950 g_free(dname);
951
952 if (result && result[0])
953 file_entry_set_filename(GTK_WIDGET(fe), result[0]);
954
955 g_strfreev(result);
956 }
957
file_entry_init(FileEntry * fe)958 static void file_entry_init(FileEntry *fe)
959 {
960 gtk_box_set_spacing(GTK_BOX(fe), 6);
961
962 fe->entry = gtk_entry_new();
963 fe->button = gtk_button_new_with_label("Browse...");
964 gtk_box_pack_start(GTK_BOX(fe), fe->entry, TRUE, TRUE, 0);
965 gtk_box_pack_start(GTK_BOX(fe), fe->button, FALSE, FALSE, 0);
966 gtk_widget_show(fe->entry);
967 gtk_widget_show(fe->button);
968
969 g_signal_connect(fe->entry, "notify::is-focus",
970 G_CALLBACK(focus_changed), fe);
971 g_signal_connect(fe->button, "clicked",
972 G_CALLBACK(browse_for_files), fe);
973 }
974
file_entry_class_init(FileEntryClass * class)975 static void file_entry_class_init(FileEntryClass *class)
976 {
977 GObjectClass *obj_class;
978
979 obj_class = G_OBJECT_CLASS(class);
980 obj_class->finalize = file_entry_finalize;
981
982 selection_changed_signal =
983 g_signal_new("selection-changed",
984 G_OBJECT_CLASS_TYPE(obj_class),
985 G_SIGNAL_RUN_LAST,
986 0, NULL, NULL,
987 g_cclosure_marshal_VOID__VOID,
988 G_TYPE_NONE, 0);
989 }
990
file_entry_new(const char * title,const char * desc1,const char * pattern1,...)991 GtkWidget * file_entry_new(const char *title,
992 const char *desc1,
993 const char *pattern1,
994 ...)
995 {
996 FileEntry *fe = g_object_new(file_entry_get_type(), NULL);
997 va_list ap;
998
999 fe->title = g_strdup(title);
1000
1001 va_start(ap, pattern1);
1002 fe->filters = build_filter_string(desc1, pattern1, ap);
1003 va_end(ap);
1004
1005 return GTK_WIDGET(fe);
1006 }
1007
1008
1009 #else /* ! GDK_WINDOWING_WIN32 */
1010
file_entry_new(const char * title,const char * desc1,const char * pattern1,...)1011 GtkWidget * file_entry_new(const char *title,
1012 const char *desc1,
1013 const char *pattern1,
1014 ...)
1015 {
1016 GtkWidget *btn;
1017 va_list ap;
1018
1019 btn = gtk_file_chooser_button_new(title, GTK_FILE_CHOOSER_ACTION_OPEN);
1020
1021 va_start(ap, pattern1);
1022 setup_file_filters(GTK_FILE_CHOOSER(btn), desc1, pattern1, ap);
1023 va_end(ap);
1024
1025 return btn;
1026 }
1027
file_entry_set_filename(GtkWidget * fe,const char * filename)1028 void file_entry_set_filename(GtkWidget *fe,
1029 const char *filename)
1030 {
1031 gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(fe), filename);
1032 }
1033
file_entry_get_filename(GtkWidget * fe)1034 char * file_entry_get_filename(GtkWidget *fe)
1035 {
1036 return gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fe));
1037 }
1038
1039 #endif /* ! GDK_WINDOWING_WIN32 */
1040