1 /** \file uismartattach.c
2 * \brief GTK3 smart-attach dialog
3 *
4 * \author Bas Wassink <b.wassink@ziggo.nl>
5 */
6
7 /*
8 * This file is part of VICE, the Versatile Commodore Emulator.
9 * See README for copyright notice.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24 * 02111-1307 USA.
25 */
26
27 #include "vice.h"
28
29 #include <gtk/gtk.h>
30
31 #include "attach.h"
32 #include "autostart.h"
33 #include "tape.h"
34 #include "debug_gtk3.h"
35 #include "basedialogs.h"
36 #include "contentpreviewwidget.h"
37 #include "diskcontents.h"
38 #include "tapecontents.h"
39 #include "filechooserhelpers.h"
40 #include "ui.h"
41 #include "uimachinewindow.h"
42 #include "lastdir.h"
43
44 #include "uismartattach.h"
45
46
47 /** \brief File type filters for the dialog
48 */
49 static ui_file_filter_t filters[] = {
50 { "All files", file_chooser_pattern_all },
51 { "Disk images", file_chooser_pattern_disk },
52 { "Tape images", file_chooser_pattern_tape },
53 { "Program files", file_chooser_pattern_program },
54 { "Archives files", file_chooser_pattern_archive },
55 { "Compressed files", file_chooser_pattern_compressed },
56 { NULL, NULL }
57 };
58
59
60 /** \brief Preview widget reference
61 */
62 static GtkWidget *preview_widget = NULL;
63
64
65 /** \brief Last directory used
66 *
67 * When an image is attached, this is set to the directory of that file. Since
68 * it's heap-allocated by Gtk3, it must be freed with a call to
69 * ui_smart_attach_shutdown() on emulator shutdown.
70 */
71 static gchar *last_dir = NULL;
72
73
74 #if 0
75 /** \brief Update the last directory reference
76 *
77 * \param[in] widget dialog
78 */
79 static void update_last_dir(GtkWidget *widget)
80 {
81 gchar *new_dir;
82
83 new_dir = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(widget));
84 debug_gtk3("new dir = '%s'.", new_dir);
85 if (new_dir != NULL) {
86 /* clean up previous value */
87 if (last_dir != NULL) {
88 g_free(last_dir);
89 }
90 last_dir = new_dir;
91 }
92 }
93 #endif
94
95
96 /** \brief Tigger autostart
97 *
98 * \param[in] widget dialog
99 */
do_autostart(GtkWidget * widget,gpointer data)100 static void do_autostart(GtkWidget *widget, gpointer data)
101 {
102 gchar *filename;
103 int index = GPOINTER_TO_INT(data);
104
105 lastdir_update(widget, &last_dir);
106 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
107 debug_gtk3("Autostarting file '%s'.", filename);
108 /* if this function exists, why is there no attach_autodetect()
109 * or something similar? -- compyx */
110 if (autostart_autodetect(
111 filename,
112 NULL, /* program name */
113 index, /* Program number? Probably used when clicking
114 in the preview widget to load the proper
115 file in an image */
116 AUTOSTART_MODE_RUN) < 0) {
117 /* oeps */
118 debug_gtk3("autostart-smart-attach failed.");
119 }
120 g_free(filename);
121 gtk_widget_destroy(widget);
122 }
123
124
125
on_file_activated(GtkWidget * chooser,gpointer data)126 static void on_file_activated(GtkWidget *chooser, gpointer data)
127 {
128 debug_gtk3("I haz called.");
129 do_autostart(chooser, data);
130 }
131
132
133
134 /** \brief Handler for the "update-preview" event
135 *
136 * \param[in] chooser file chooser dialog
137 * \param[in] data extra event data (unused)
138 */
on_update_preview(GtkFileChooser * chooser,gpointer data)139 static void on_update_preview(GtkFileChooser *chooser, gpointer data)
140 {
141 GFile *file;
142 gchar *path;
143
144 file = gtk_file_chooser_get_preview_file(chooser);
145 if (file != NULL) {
146 path = g_file_get_path(file);
147 if (path != NULL) {
148 debug_gtk3("called with '%s'.", path);
149
150 content_preview_widget_set_image(preview_widget, path);
151 g_free(path);
152 }
153 g_object_unref(file);
154 }
155 }
156
157 /** \brief Handler for the 'toggled' event of the 'show hidden files' checkbox
158 *
159 * \param[in] widget checkbox triggering the event
160 * \param[in] user_data data for the event (the dialog)
161 */
on_hidden_toggled(GtkWidget * widget,gpointer user_data)162 static void on_hidden_toggled(GtkWidget *widget, gpointer user_data)
163 {
164 int state;
165
166 state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
167 debug_gtk3("show hidden files: %s.", state ? "enabled" : "disabled");
168
169 gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(user_data), state);
170 }
171
172
173
174 /** \brief Handler for the 'toggled' event of the 'show preview' checkbox
175 *
176 * \param[in] widget checkbox triggering the event
177 * \param[in] user_data data for the event (unused)
178 */
on_preview_toggled(GtkWidget * widget,gpointer user_data)179 static void on_preview_toggled(GtkWidget *widget, gpointer user_data)
180 {
181 #ifdef HAVE_DEBUG_GTK3UI
182 int state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
183 #endif
184 debug_gtk3("preview %s.", state ? "enabled" : "disabled");
185 /* TODO: actually disable the preview widget and resize the dialog */
186 }
187
188
189 /** \brief Handler for 'response' event of the dialog
190 *
191 * This handler is called when the user clicks a button in the dialog.
192 *
193 * \param[in] widget the dialog
194 * \param[in] response_id response ID
195 * \param[in] user_data extra data (unused)
196 *
197 * TODO: proper (error) messages, which requires implementing ui_error() and
198 * ui_message() and moving them into gtk3/widgets to avoid circular
199 * references
200 */
on_response(GtkWidget * widget,gint response_id,gpointer user_data)201 static void on_response(GtkWidget *widget, gint response_id, gpointer user_data)
202 {
203 gchar *filename;
204 int index;
205
206 index = GPOINTER_TO_INT(user_data);
207
208 debug_gtk3("got response ID %d, index %d.", response_id, index);
209
210 switch (response_id) {
211
212 /* 'Open' button, double-click on file */
213 case GTK_RESPONSE_ACCEPT:
214 lastdir_update(widget, &last_dir);
215 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
216 /* ui_message("Opening file '%s' ...", filename); */
217 debug_gtk3("Opening file '%s'.", filename);
218
219 /* copied from Gtk2: I fail to see how brute-forcing your way
220 * through file types is 'smart', but hell, it works */
221 if (file_system_attach_disk(8, filename) < 0
222 && tape_image_attach(1, filename) < 0
223 && autostart_snapshot(filename, NULL) < 0
224 && autostart_prg(filename, AUTOSTART_MODE_LOAD) < 0) {
225 /* failed */
226 debug_gtk3("smart attach failed.");
227 }
228
229 g_free(filename);
230 gtk_widget_destroy(widget);
231 break;
232
233 /* 'Autostart' button clicked */
234 case VICE_RESPONSE_AUTOSTART:
235 do_autostart(widget, user_data);
236 #if 0
237 lastdir_update(widget, &last_dir);
238 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
239 debug_gtk3("Autostarting file '%s'.", filename);
240 /* if this function exists, why is there no attach_autodetect()
241 * or something similar? -- compyx */
242 if (autostart_autodetect(
243 filename,
244 NULL, /* program name */
245 index, /* Program number? Probably used when clicking
246 in the preview widget to load the proper
247 file in an image */
248 AUTOSTART_MODE_RUN) < 0) {
249 /* oeps */
250 debug_gtk3("autostart-smart-attach failed.");
251 }
252 g_free(filename);
253 gtk_widget_destroy(widget);
254 #endif
255 break;
256
257 /* 'Close'/'X' button */
258 case GTK_RESPONSE_REJECT:
259 gtk_widget_destroy(widget);
260 break;
261 default:
262 break;
263 }
264
265 ui_set_ignore_mouse_hide(FALSE);
266 }
267
268
269 /** \brief Create the 'extra' widget
270 *
271 * \return GtkGrid
272 */
create_extra_widget(GtkWidget * parent)273 static GtkWidget *create_extra_widget(GtkWidget *parent)
274 {
275 GtkWidget *grid;
276 GtkWidget *hidden_check;
277 GtkWidget *readonly_check;
278 GtkWidget *preview_check;
279
280 grid = gtk_grid_new();
281 gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
282
283 hidden_check = gtk_check_button_new_with_label("Show hidden files");
284 g_signal_connect(hidden_check, "toggled", G_CALLBACK(on_hidden_toggled),
285 (gpointer)(parent));
286 gtk_grid_attach(GTK_GRID(grid), hidden_check, 0, 0, 1, 1);
287
288 readonly_check = gtk_check_button_new_with_label("Attach read-only");
289 gtk_grid_attach(GTK_GRID(grid), readonly_check, 1, 0, 1, 1);
290
291 preview_check = gtk_check_button_new_with_label("Show image contents");
292 g_signal_connect(preview_check, "toggled", G_CALLBACK(on_preview_toggled),
293 NULL);
294 gtk_grid_attach(GTK_GRID(grid), preview_check, 2, 0, 1, 1);
295
296 gtk_widget_show_all(grid);
297 return grid;
298 }
299
300
301 /** \brief Wrapper around disk/tape contents readers
302 *
303 * First treats \a path as disk image file and when that fails it falls back
304 * to treating \a path as a tape image, when that fails as well, it gives up.
305 *
306 * \param[in] path path to image file
307 *
308 * \return image contents or `NULL` on failure
309 */
read_contents_wrapper(const char * path)310 static image_contents_t *read_contents_wrapper(const char *path)
311 {
312 image_contents_t *content;
313
314 /* try disk contents first */
315 content = diskcontents_filesystem_read(path);
316 if (content == NULL) {
317 /* fall back to tape */
318 content = tapecontents_read(path);
319 }
320 return content;
321 }
322
323
324 /** \brief Create the smart-attach dialog
325 *
326 * \param[in] parent parent widget, used to get the top level window
327 *
328 * \return GtkFileChooserDialog
329 */
create_smart_attach_dialog(GtkWidget * parent)330 static GtkWidget *create_smart_attach_dialog(GtkWidget *parent)
331 {
332 GtkWidget *dialog;
333 size_t i;
334
335 ui_set_ignore_mouse_hide(TRUE);
336
337 /* create new dialog */
338 dialog = gtk_file_chooser_dialog_new(
339 "Smart-attach a file",
340 ui_get_active_window(),
341 GTK_FILE_CHOOSER_ACTION_OPEN,
342 /* buttons */
343 "Open", GTK_RESPONSE_ACCEPT,
344 "Autostart", VICE_RESPONSE_AUTOSTART,
345 "Close", GTK_RESPONSE_REJECT,
346 NULL, NULL);
347
348 /* set modal so mouse-grab doesn't get triggered */
349 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
350
351 /* set last used directory */
352 lastdir_set(dialog, &last_dir);
353
354 /* add 'extra' widget: 'readony' and 'show preview' checkboxes */
355 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog),
356 create_extra_widget(dialog));
357
358 preview_widget = content_preview_widget_create(dialog,
359 read_contents_wrapper, on_response);
360 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog),
361 preview_widget);
362
363 /* add filters */
364 for (i = 0; filters[i].name != NULL; i++) {
365 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
366 create_file_chooser_filter(filters[i], FALSE));
367 }
368
369 /* connect "reponse" handler: the `user_data` argument gets filled in when
370 * the "response" signal is emitted: a response ID */
371 g_signal_connect(dialog, "response", G_CALLBACK(on_response), NULL);
372 g_signal_connect(dialog, "update-preview",
373 G_CALLBACK(on_update_preview), NULL);
374 g_signal_connect(dialog, "file-activated",
375 G_CALLBACK(on_file_activated), NULL);
376
377 return dialog;
378
379 }
380
381
382 /** \brief Callback for the File menu's "smart-attach" item
383 *
384 * Creates the smart-dialog and runs it.
385 *
386 * \param[in] widget menu item triggering the callback
387 * \param[in] user_data data for the event (unused)
388 */
ui_smart_attach_callback(GtkWidget * widget,gpointer user_data)389 void ui_smart_attach_callback(GtkWidget *widget, gpointer user_data)
390 {
391 GtkWidget *dialog;
392
393 debug_gtk3("called.");
394
395 dialog = create_smart_attach_dialog(widget);
396
397 gtk_widget_show(dialog);
398
399 }
400
401
402 /** \brief Clean up last used directory string
403 */
ui_smart_attach_shutdown(void)404 void ui_smart_attach_shutdown(void)
405 {
406 lastdir_shutdown(&last_dir);
407 }
408