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