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