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