1 /** \file   uidiskattach.c
2  * \brief   GTK3 disk-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 "imagecontents.h"
38 #include "diskcontents.h"
39 #include "filechooserhelpers.h"
40 #include "driveunitwidget.h"
41 #include "ui.h"
42 #include "uimachinewindow.h"
43 #include "lastdir.h"
44 
45 #include "uidiskattach.h"
46 
47 
48 /** \brief  File type filters for the dialog
49  */
50 static ui_file_filter_t filters[] = {
51     { "Disk images", file_chooser_pattern_disk },
52     { "Compressed files", file_chooser_pattern_compressed },
53     { "All files", file_chooser_pattern_all },
54     { NULL, NULL }
55 };
56 
57 
58 /** \brief  Preview widget reference
59  */
60 static GtkWidget *preview_widget = NULL;
61 
62 
63 /** \brief  Last directory used
64  *
65  * When a disk is attached, this is set to the directory of that file. Since
66  * it's heap-allocated by Gtk3, it must be freed with a call to
67  * ui_disk_attach_shutdown() on emulator shutdown.
68  */
69 static gchar *last_dir = NULL;
70 
71 
72 
73 /** \brief  Unit number to attach disk to
74  */
75 static int unit_number = 8;
76 
77 #if 0
78 /** \brief  Update the last directory reference
79  *
80  * \param[in]   widget  dialog
81  */
82 static void update_last_dir(GtkWidget *widget)
83 {
84     gchar *new_dir;
85 
86     new_dir = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(widget));
87     debug_gtk3("new dir = '%s'.", new_dir);
88     if (new_dir != NULL) {
89         /* clean up previous value */
90         if (last_dir != NULL) {
91             g_free(last_dir);
92         }
93         last_dir = new_dir;
94     }
95 }
96 #endif
97 
98 /** \brief  Handler for the 'toggled' event of the 'show hidden files' checkbox
99  *
100  * \param[in]   widget      checkbox triggering the event
101  * \param[in]   user_data   data for the event (the dialog)
102  */
on_hidden_toggled(GtkWidget * widget,gpointer user_data)103 static void on_hidden_toggled(GtkWidget *widget, gpointer user_data)
104 {
105     int state;
106 
107     state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
108     debug_gtk3("show hidden files: %s.", state ? "enabled" : "disabled");
109 
110     gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(user_data), state);
111 }
112 
113 
114 #if 0
115 /** \brief  Handler for the 'toggled' event of the 'show preview' checkbox
116  *
117  * \param[in]   widget      checkbox triggering the event
118  * \param[in]   user_data   data for the event (unused)
119  */
120 static void on_preview_toggled(GtkWidget *widget, gpointer user_data)
121 {
122     int state;
123 
124     state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
125     debug_gtk3("preview %s.", state ? "enabled" : "disabled");
126 }
127 #endif
128 
129 
130 
131 /** \brief  Handler for the "update-preview" event
132  *
133  * \param[in]   chooser file chooser dialog
134  * \param[in]   data    extra event data (unused)
135  */
on_update_preview(GtkFileChooser * chooser,gpointer data)136 static void on_update_preview(GtkFileChooser *chooser, gpointer data)
137 {
138     GFile *file;
139     gchar *path;
140 
141     file = gtk_file_chooser_get_preview_file(chooser);
142     if (file != NULL) {
143         path = g_file_get_path(file);
144         if (path != NULL) {
145             debug_gtk3("called with '%s'.", path);
146 
147             content_preview_widget_set_image(preview_widget, path);
148            g_free(path);
149         }
150         g_object_unref(file);
151     }
152 }
153 
154 
155 
do_autostart(GtkWidget * widget,gpointer user_data)156 static void do_autostart(GtkWidget *widget, gpointer user_data)
157 {
158     gchar *filename;
159     int index = GPOINTER_TO_INT(user_data);
160 
161     lastdir_update(widget, &last_dir);
162     filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
163     debug_gtk3("Autostarting file '%s'.", filename);
164     /* if this function exists, why is there no attach_autodetect()
165      * or something similar? -- compyx */
166     if (autostart_disk(
167                 filename,
168                 NULL,   /* program name */
169                 index,  /* Program number? Probably used when clicking
170                            in the preview widget to load the proper
171                            file in an image */
172                 AUTOSTART_MODE_RUN) < 0) {
173         /* oeps */
174         debug_gtk3("autostart disk attach failed.");
175     }
176     g_free(filename);
177     gtk_widget_destroy(widget);
178 }
179 
180 
on_file_activated(GtkWidget * chooser,gpointer data)181 static void on_file_activated(GtkWidget *chooser, gpointer data)
182 {
183     do_autostart(chooser, data);
184 }
185 
186 
187 
188 /** \brief  Handler for 'response' event of the dialog
189  *
190  * This handler is called when the user clicks a button in the dialog.
191  *
192  * \param[in]   widget      the dialog
193  * \param[in]   response_id response ID
194  * \param[in]   user_data   extra data (unused)
195  *
196  * TODO:    proper (error) messages, which requires implementing ui_error() and
197  *          ui_message() and moving them into gtk3/widgets to avoid circular
198  *          references
199  */
on_response(GtkWidget * widget,gint response_id,gpointer user_data)200 static void on_response(GtkWidget *widget, gint response_id,
201                         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("Attaching file '%s' to unit #%d.",
218                     filename, unit_number);
219 
220             /* copied from Gtk2: I fail to see how brute-forcing your way
221              * through file types is 'smart', but hell, it works */
222             if (file_system_attach_disk(unit_number, filename) < 0) {
223                 /* failed */
224                 debug_gtk3("disk attach failed.");
225             }
226             g_free(filename);
227             gtk_widget_destroy(widget);
228             break;
229 
230         /* 'Autostart' button clicked */
231         case VICE_RESPONSE_AUTOSTART:
232             do_autostart(widget, user_data);
233            break;
234 
235         /* 'Close'/'X' button */
236         case GTK_RESPONSE_REJECT:
237             gtk_widget_destroy(widget);
238             break;
239         default:
240             break;
241     }
242 
243     ui_set_ignore_mouse_hide(FALSE);
244 }
245 
246 
247 /** \brief  Create the 'extra' widget
248  *
249  * \return  GtkGrid
250  *
251  * TODO: 'grey-out'/disable units without a proper drive attached
252  */
create_extra_widget(GtkWidget * parent,int unit)253 static GtkWidget *create_extra_widget(GtkWidget *parent, int unit)
254 {
255     GtkWidget *grid;
256     GtkWidget *hidden_check;
257     GtkWidget *readonly_check;
258 #if 0
259     GtkWidget *preview_check;
260 #endif
261 
262     grid = gtk_grid_new();
263     gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
264 
265     hidden_check = gtk_check_button_new_with_label("Show hidden files");
266     g_signal_connect(hidden_check, "toggled", G_CALLBACK(on_hidden_toggled),
267             (gpointer)(parent));
268     gtk_grid_attach(GTK_GRID(grid), hidden_check, 0, 0, 1, 1);
269 
270     readonly_check = gtk_check_button_new_with_label("Attach read-only");
271     gtk_grid_attach(GTK_GRID(grid), readonly_check, 1, 0, 1, 1);
272 
273 #if 0
274     preview_check = gtk_check_button_new_with_label("Show image contents");
275     g_signal_connect(preview_check, "toggled", G_CALLBACK(on_preview_toggled),
276             NULL);
277     gtk_grid_attach(GTK_GRID(grid), preview_check, 2, 0, 1, 1);
278 #endif
279     /* second row, three cols wide */
280     gtk_grid_attach(GTK_GRID(grid),
281             drive_unit_widget_create(unit, &unit_number, NULL),
282             0, 1, 3, 1);
283 
284     gtk_widget_show_all(grid);
285     return grid;
286 }
287 
288 
289 /** \brief  Create the disk attach dialog
290  *
291  * \param[in]   parent  parent widget, used to get the top level window
292  *
293  * \return  GtkFileChooserDialog
294  */
create_disk_attach_dialog(GtkWidget * parent,int unit)295 static GtkWidget *create_disk_attach_dialog(GtkWidget *parent, int unit)
296 {
297     GtkWidget *dialog;
298     size_t i;
299 
300     ui_set_ignore_mouse_hide(TRUE);
301 
302     /* create new dialog */
303     dialog = gtk_file_chooser_dialog_new(
304             "Attach a disk image",
305             ui_get_active_window(),
306             GTK_FILE_CHOOSER_ACTION_OPEN,
307             /* buttons */
308             "Open", GTK_RESPONSE_ACCEPT,
309             "Autostart", VICE_RESPONSE_AUTOSTART,
310             "Close", GTK_RESPONSE_REJECT,
311             NULL, NULL);
312 
313     /* set modal so mouse-grab doesn't get triggered */
314     gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
315 
316     /* set last directory */
317     lastdir_set(dialog, &last_dir);
318 
319     /* add 'extra' widget: 'readony' and 'show preview' checkboxes */
320     if (unit < 8 || unit > 11) {
321         unit = 8;
322     }
323     gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog),
324                                       create_extra_widget(dialog, unit));
325 
326     preview_widget = content_preview_widget_create(
327             dialog, diskcontents_filesystem_read, on_response);
328     gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog),
329             preview_widget);
330 
331     /* add filters */
332     for (i = 0; filters[i].name != NULL; i++) {
333         gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
334                 create_file_chooser_filter(filters[i], FALSE));
335     }
336 
337     /* connect "reponse" handler: the `user_data` argument gets filled in when
338      * the "response" signal is emitted: a response ID */
339     g_signal_connect(dialog, "response",
340             G_CALLBACK(on_response), GINT_TO_POINTER(0));
341     g_signal_connect(dialog, "update-preview",
342             G_CALLBACK(on_update_preview), NULL);
343     g_signal_connect(dialog, "file-activated",
344             G_CALLBACK(on_file_activated), NULL);
345     return dialog;
346 
347 }
348 
349 
350 /** \brief  Callback for the "smart-attach" and "attach to #%d" menu items
351  *
352  * Creates the smart-dialog and runs it.
353  *
354  * \param[in]   widget      menu item triggering the callback
355  * \param[in]   user_data   integer from 8-11 for the default drive to attach
356  *                          (for some reason auto-attach always ultra uses #8)
357  */
ui_disk_attach_callback(GtkWidget * widget,gpointer user_data)358 void ui_disk_attach_callback(GtkWidget *widget, gpointer user_data)
359 {
360     GtkWidget *dialog;
361 
362     dialog = create_disk_attach_dialog(widget, GPOINTER_TO_INT(user_data));
363     gtk_widget_show(dialog);
364 
365 }
366 
367 /** \brief  Callback for "detach from #%d" menu items
368  *
369  * Removes any disk from the specified drive. No additional UI is
370  * presented.
371  *
372  * \param[in]   widget      menu item triggering the callback
373  * \param[in]   user_data   integer from 8-11 for the drive to
374  *                          close, or -1 to detach all disks
375  */
ui_disk_detach_callback(GtkWidget * widget,gpointer user_data)376 void ui_disk_detach_callback(GtkWidget *widget, gpointer user_data)
377 {
378     /* This function does its own interpretation and input validation,
379      * so we can simply forward the call directly. */
380     file_system_detach_disk(GPOINTER_TO_INT(user_data));
381 }
382 
383 
384 /** \brief  Detach all disks
385  *
386  * \param[in]   widget  widget (unused)
387  * \param[in]   data    extra event data (unused)
388  */
ui_disk_detach_all_callback(GtkWidget * widget,gpointer data)389 void ui_disk_detach_all_callback(GtkWidget *widget, gpointer data)
390 {
391     int unit;
392 
393     /* TODO: figure out where these integer literals are defined */
394     for (unit = 8; unit < 12; unit++) {
395         file_system_detach_disk(unit);
396     }
397 }
398 
399 
400 /** \brief  Clean up the last directory string
401  */
ui_disk_attach_shutdown(void)402 void ui_disk_attach_shutdown(void)
403 {
404     lastdir_shutdown(&last_dir);
405 }
406