1 /** \file uitapeattach.c
2 * \brief GTK3 tape-attach dialog
3 *
4 * \author Bas Wassink <b.wassink@ziggo.nl>
5 * \author Michael C. Martin <mcmartin@gmail.com>
6 */
7
8 /*
9 * This file is part of VICE, the Versatile Commodore Emulator.
10 * See README for copyright notice.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
25 * 02111-1307 USA.
26 */
27
28 #include "vice.h"
29
30 #include <gtk/gtk.h>
31
32 #include "attach.h"
33 #include "autostart.h"
34 #include "tape.h"
35 #include "debug_gtk3.h"
36 #include "basedialogs.h"
37 #include "contentpreviewwidget.h"
38 #include "filechooserhelpers.h"
39 #include "imagecontents.h"
40 #include "lastdir.h"
41 #include "tapecontents.h"
42 #include "ui.h"
43 #include "uimachinewindow.h"
44
45 #include "uitapeattach.h"
46
47
48 /** \brief File type filters for the dialog
49 */
50 static ui_file_filter_t filters[] = {
51 { "Tape images", file_chooser_pattern_tape },
52 { "All files", file_chooser_pattern_all },
53 { NULL, NULL }
54 };
55
56 static GtkWidget *preview_widget = NULL;
57
58
59 /** \brief Last directory used
60 *
61 * When a taoe is attached, this is set to the directory of that file. Since
62 * it's heap-allocated by Gtk3, it must be freed with a call to
63 * ui_tape_attach_shutdown() on emulator shutdown.
64 */
65 static gchar *last_dir = NULL;
66
67
68 /** \brief Handler for the "update-preview" event
69 *
70 * \param[in] chooser file chooser dialog
71 * \param[in] data extra event data (unused)
72 */
on_update_preview(GtkFileChooser * chooser,gpointer data)73 static void on_update_preview(GtkFileChooser *chooser, gpointer data)
74 {
75 GFile *file;
76 gchar *path;
77
78 file = gtk_file_chooser_get_preview_file(chooser);
79 if (file != NULL) {
80 path = g_file_get_path(file);
81 if (path != NULL) {
82 debug_gtk3("called with '%s'.", path);
83
84 content_preview_widget_set_image(preview_widget, path);
85 g_free(path);
86 }
87 g_object_unref(file);
88 }
89 }
90
91
92 /** \brief Handler for the 'toggled' event of the 'show hidden files' checkbox
93 *
94 * \param[in] widget checkbox triggering the event
95 * \param[in] user_data data for the event (the dialog)
96 */
on_hidden_toggled(GtkWidget * widget,gpointer user_data)97 static void on_hidden_toggled(GtkWidget *widget, gpointer user_data)
98 {
99 int state;
100
101 state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
102 debug_gtk3("show hidden files: %s.", state ? "enabled" : "disabled");
103
104 gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(user_data), state);
105 }
106
107
108 /** \brief Handler for 'response' event of the dialog
109 *
110 * This handler is called when the user clicks a button in the dialog.
111 *
112 * \param[in] widget the dialog
113 * \param[in] response_id response ID
114 * \param[in] user_data extra data (unused)
115 *
116 * TODO: proper (error) messages, which requires implementing ui_error() and
117 * ui_message() and moving them into gtk3/widgets to avoid circular
118 * references
119 */
on_response(GtkWidget * widget,gint response_id,gpointer user_data)120 static void on_response(GtkWidget *widget, gint response_id,
121 gpointer user_data)
122 {
123 gchar *filename;
124 int index;
125
126 index = GPOINTER_TO_INT(user_data);
127
128 debug_gtk3("got response ID %d, index %d.", response_id, index);
129
130 switch (response_id) {
131
132 /* 'Open' button, double-click on file */
133 case GTK_RESPONSE_ACCEPT:
134 lastdir_update(widget, &last_dir);
135 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
136 /* ui_message("Opening file '%s' ...", filename); */
137 debug_gtk3("Attaching file '%s' to tape unit.", filename);
138
139 /* copied from Gtk2: I fail to see how brute-forcing your way
140 * through file types is 'smart', but hell, it works */
141 if (tape_image_attach(1, filename) < 0) {
142 /* failed */
143 debug_gtk3("tape attach failed.");
144 }
145 g_free(filename);
146 gtk_widget_destroy(widget);
147 break;
148
149 /* 'Autostart' button clicked */
150 case VICE_RESPONSE_AUTOSTART:
151 lastdir_update(widget, &last_dir);
152 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
153 debug_gtk3("Autostarting file '%s'.", filename);
154 if (autostart_tape(
155 filename,
156 NULL, /* program name */
157 index,
158 AUTOSTART_MODE_RUN) < 0) {
159 /* oeps */
160 debug_gtk3("autostart tape attach failed.");
161 }
162 g_free(filename);
163 gtk_widget_destroy(widget);
164 break;
165
166 /* 'Close'/'X' button */
167 case GTK_RESPONSE_REJECT:
168 gtk_widget_destroy(widget);
169 break;
170 default:
171 break;
172 }
173
174 ui_set_ignore_mouse_hide(FALSE);
175 }
176
177
178 /** \brief Create the 'extra' widget
179 *
180 * \return GtkGrid
181 *
182 * TODO: 'grey-out'/disable units without a proper drive attached
183 */
create_extra_widget(GtkWidget * parent)184 static GtkWidget *create_extra_widget(GtkWidget *parent)
185 {
186 GtkWidget *grid;
187 GtkWidget *hidden_check;
188 GtkWidget *readonly_check;
189 #if 0
190 GtkWidget *preview_check;
191 #endif
192
193 grid = gtk_grid_new();
194 gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
195
196 hidden_check = gtk_check_button_new_with_label("Show hidden files");
197 g_signal_connect(hidden_check, "toggled", G_CALLBACK(on_hidden_toggled),
198 (gpointer)(parent));
199 gtk_grid_attach(GTK_GRID(grid), hidden_check, 0, 0, 1, 1);
200
201 readonly_check = gtk_check_button_new_with_label("Attach read-only");
202 gtk_grid_attach(GTK_GRID(grid), readonly_check, 1, 0, 1, 1);
203 #if 0
204 preview_check = gtk_check_button_new_with_label("Show image contents");
205 g_signal_connect(preview_check, "toggled", G_CALLBACK(on_preview_toggled),
206 NULL);
207 gtk_grid_attach(GTK_GRID(grid), preview_check, 2, 0, 1, 1);
208 #endif
209
210 gtk_widget_show_all(grid);
211 return grid;
212 }
213
214
215 /** \brief Create the tape attach dialog
216 *
217 * \param[in] parent parent widget, used to get the top level window
218 *
219 * \return GtkFileChooserDialog
220 */
create_tape_attach_dialog(GtkWidget * parent)221 static GtkWidget *create_tape_attach_dialog(GtkWidget *parent)
222 {
223 GtkWidget *dialog;
224 size_t i;
225
226 ui_set_ignore_mouse_hide(TRUE);
227
228 /* create new dialog */
229 dialog = gtk_file_chooser_dialog_new(
230 "Attach a tape image",
231 ui_get_active_window(),
232 GTK_FILE_CHOOSER_ACTION_OPEN,
233 /* buttons */
234 "Open", GTK_RESPONSE_ACCEPT,
235 "Autostart", VICE_RESPONSE_AUTOSTART,
236 "Close", GTK_RESPONSE_REJECT,
237 NULL, NULL);
238
239 /* set modal so mouse-grab doesn't get triggered */
240 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
241
242 /* set last directory */
243 lastdir_set(dialog, &last_dir);
244
245 /* add 'extra' widget: 'readonly' and 'show preview' checkboxes */
246 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog),
247 create_extra_widget(dialog));
248
249 preview_widget = content_preview_widget_create(dialog, tapecontents_read,
250 on_response);
251 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog),
252 preview_widget);
253
254 /* add filters */
255 for (i = 0; filters[i].name != NULL; i++) {
256 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
257 create_file_chooser_filter(filters[i], FALSE));
258 }
259
260 /* connect "reponse" handler: the `user_data` argument gets filled in when
261 * the "response" signal is emitted: a response ID */
262 g_signal_connect(dialog, "response", G_CALLBACK(on_response), NULL);
263 g_signal_connect(dialog, "update-preview", G_CALLBACK(on_update_preview),
264 NULL);
265
266 return dialog;
267
268 }
269
270
271 /** \brief Callback for the "attach tape image" menu items
272 *
273 * Creates the dialog and runs it.
274 *
275 * \param[in] widget menu item triggering the callback
276 * \param[in] user_data ignored
277 */
ui_tape_attach_callback(GtkWidget * widget,gpointer user_data)278 void ui_tape_attach_callback(GtkWidget *widget, gpointer user_data)
279 {
280 GtkWidget *dialog;
281
282 debug_gtk3("called.");
283 dialog = create_tape_attach_dialog(widget);
284 gtk_widget_show(dialog);
285
286 }
287
288
289 /** \brief Callback for "detach tape image" menu items
290 *
291 * Removes any tape from the specified drive. No additional UI is
292 * presented.
293 *
294 * \param[in] widget menu item triggering the callback
295 * \param[in] user_data ignored
296 */
ui_tape_detach_callback(GtkWidget * widget,gpointer user_data)297 void ui_tape_detach_callback(GtkWidget *widget, gpointer user_data)
298 {
299 tape_image_detach(1);
300 }
301
302
303 /** \brief Clean up the last directory string
304 */
ui_tape_attach_shutdown(void)305 void ui_tape_attach_shutdown(void)
306 {
307 lastdir_shutdown(&last_dir);
308 }
309