1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      GTK native dialog implementation.
12  *
13  *      See LICENSE.txt for copyright information.
14  */
15 
16 #include <gtk/gtk.h>
17 
18 #include "allegro5/allegro.h"
19 #include "allegro5/allegro_native_dialog.h"
20 #include "allegro5/internal/aintern_native_dialog.h"
21 #include "gtk_dialog.h"
22 #include "gtk_xgtk.h"
23 
24 typedef struct {
25    ALLEGRO_DISPLAY         *display;
26    ALLEGRO_NATIVE_DIALOG   *dialog;
27 } GTK_FILE_DIALOG_MESSAGE;
28 
29 /* [nd_gtk thread] */
create_gtk_file_dialog(gpointer data)30 static gboolean create_gtk_file_dialog(gpointer data)
31 {
32    GTK_FILE_DIALOG_MESSAGE *msg = data;
33    ALLEGRO_DISPLAY *display = msg->display;
34    ALLEGRO_NATIVE_DIALOG *fd = msg->dialog;
35    bool save = fd->flags & ALLEGRO_FILECHOOSER_SAVE;
36    bool folder = fd->flags & ALLEGRO_FILECHOOSER_FOLDER;
37    gint result;
38 
39    GtkWidget *window;
40 
41    window =
42       gtk_file_chooser_dialog_new(al_cstr(fd->title),
43                                   NULL,
44                                   save ? GTK_FILE_CHOOSER_ACTION_SAVE : folder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : GTK_FILE_CHOOSER_ACTION_OPEN,
45                                   "_Cancel", GTK_RESPONSE_CANCEL,
46                                   save ? "_Save" : "_Open", GTK_RESPONSE_ACCEPT, NULL);
47 
48    _al_gtk_make_transient(display, window);
49 
50    if (save) {
51       gtk_file_chooser_set_do_overwrite_confirmation
52          (GTK_FILE_CHOOSER(window), true);
53    }
54 
55    if (fd->fc_initial_path) {
56       bool is_dir;
57       bool exists;
58       const char *path = al_path_cstr(fd->fc_initial_path, ALLEGRO_NATIVE_PATH_SEP);
59 
60       if (al_filename_exists(path)) {
61          exists = true;
62          ALLEGRO_FS_ENTRY *fs = al_create_fs_entry(path);
63          is_dir = al_get_fs_entry_mode(fs) & ALLEGRO_FILEMODE_ISDIR;
64          al_destroy_fs_entry(fs);
65       }
66       else {
67          exists = false;
68          is_dir = false;
69       }
70 
71       if (is_dir) {
72          gtk_file_chooser_set_current_folder
73             (GTK_FILE_CHOOSER(window),
74              al_path_cstr(fd->fc_initial_path, ALLEGRO_NATIVE_PATH_SEP));
75       }
76       else if (exists) {
77          gtk_file_chooser_set_filename
78              (GTK_FILE_CHOOSER(window),
79               al_path_cstr(fd->fc_initial_path, ALLEGRO_NATIVE_PATH_SEP));
80       }
81       else {
82          ALLEGRO_PATH *dir_path = al_clone_path(fd->fc_initial_path);
83          if (dir_path) {
84             al_set_path_filename(dir_path, NULL);
85             gtk_file_chooser_set_current_folder
86                (GTK_FILE_CHOOSER(window),
87                 al_path_cstr(dir_path, ALLEGRO_NATIVE_PATH_SEP));
88             if (save) {
89                gtk_file_chooser_set_current_name
90                   (GTK_FILE_CHOOSER(window),
91                    al_get_path_filename(fd->fc_initial_path));
92             }
93             al_destroy_path(dir_path);
94          }
95       }
96    }
97 
98    if (fd->flags & ALLEGRO_FILECHOOSER_MULTIPLE)
99       gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(window), true);
100 
101    /* FIXME: Move all this filter parsing stuff into a common file. */
102    if (al_ustr_size(fd->fc_patterns) > 0) {
103       GtkFileFilter* filter = gtk_file_filter_new();
104       int start = 0;
105       int end = 0;
106       bool is_mime_type = false;
107       while (true) {
108          int32_t c = al_ustr_get(fd->fc_patterns, end);
109          if (c < 0 || c == ';') {
110             if (end - start > 0) {
111                ALLEGRO_USTR* pattern = al_ustr_dup_substr(fd->fc_patterns, start, end);
112                if (is_mime_type) {
113                   gtk_file_filter_add_mime_type(filter, al_cstr(pattern));
114                }
115                else {
116                   gtk_file_filter_add_pattern(filter, al_cstr(pattern));
117                }
118                al_ustr_free(pattern);
119             }
120             start = end + 1;
121             is_mime_type = false;
122          }
123          if (c == '/')
124             is_mime_type = true;
125          if (c < 0)
126             break;
127          end += al_utf8_width(c);
128       }
129 
130       gtk_file_filter_set_name(filter, "All supported files");
131       gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(window), filter);
132    }
133 
134    result = gtk_dialog_run(GTK_DIALOG(window));
135    if (result == GTK_RESPONSE_ACCEPT) {
136       GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(window));
137       int i;
138       GSList* iter;
139 
140       fd->fc_path_count = g_slist_length(filenames);
141       fd->fc_paths = al_malloc(fd->fc_path_count * sizeof(void *));
142       for (i = 0, iter = filenames; i < (int)fd->fc_path_count; i++, iter = g_slist_next(iter)) {
143          fd->fc_paths[i] = al_create_path((const char*)iter->data);
144          g_free(iter->data);
145       }
146       g_slist_free(filenames);
147    }
148 
149    gtk_widget_destroy(window);
150 
151    ASSERT(fd->async_queue);
152    g_async_queue_push(fd->async_queue, ACK_CLOSED);
153 
154    return FALSE;
155 }
156 
157 /* [user thread] */
_al_show_native_file_dialog(ALLEGRO_DISPLAY * display,ALLEGRO_NATIVE_DIALOG * fd)158 bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display,
159    ALLEGRO_NATIVE_DIALOG *fd)
160 {
161    GTK_FILE_DIALOG_MESSAGE msg;
162 
163    if (!_al_gtk_ensure_thread())
164       return false;
165 
166    fd->async_queue = g_async_queue_new();
167 
168    msg.display = display;
169    msg.dialog = fd;
170    g_timeout_add(0, create_gtk_file_dialog, &msg);
171 
172    /* Wait for a signal that the window is closed. */
173    while (g_async_queue_pop(fd->async_queue) != ACK_CLOSED)
174       ;
175    g_async_queue_unref(fd->async_queue);
176    fd->async_queue = NULL;
177    return true;
178 }
179 
180 /* vim: set sts=3 sw=3 et: */
181