1 /** \file   uidiskcreate.c
2  * \brief   Gtk3 dialog to create and attach a new disk image
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 #include <gtk/gtk.h>
29 #include <string.h>
30 
31 #include "basewidgets.h"
32 #include "basedialogs.h"
33 #include "debug_gtk3.h"
34 #include "widgethelpers.h"
35 #include "driveunitwidget.h"
36 #include "diskimage.h"
37 #include "filechooserhelpers.h"
38 #include "util.h"
39 #include "lib.h"
40 #include "charset.h"
41 #include "attach.h"
42 #include "vdrive/vdrive-internal.h"
43 #include "imagecontents.h"
44 #include "resources.h"
45 #include "ui.h"
46 
47 #include "uidiskcreate.h"
48 
49 
50 /** \brief  Struct holding image type names and IDs
51  */
52 typedef struct disk_image_type_s {
53     const char *name;   /**< name (ext) */
54     int id;             /**< image type ID */
55 } disk_image_type_t;
56 
57 
58 /* forward declaration */
59 static gboolean create_disk_image(const char *filename);
60 
61 
62 /** \brief  List of supported disk image types
63  *
64  * XXX: perhaps some function in diskimage.c or so producing a list of
65  *      currently supported images types like this one would be better, that
66  *      would avoid having to update UI's when a new image type is added or
67  *      removed.
68  */
69 static disk_image_type_t disk_image_types[] = {
70     { "d64", DISK_IMAGE_TYPE_D64 },
71     { "d67", DISK_IMAGE_TYPE_D67 },
72     { "d71", DISK_IMAGE_TYPE_D71 },
73     { "d80", DISK_IMAGE_TYPE_D80 },
74     { "d81", DISK_IMAGE_TYPE_D81 },
75     { "d82", DISK_IMAGE_TYPE_D82 },
76     { "d1m", DISK_IMAGE_TYPE_D1M },
77     { "d2m", DISK_IMAGE_TYPE_D2M },
78     { "d4m", DISK_IMAGE_TYPE_D4M },
79     { "g64", DISK_IMAGE_TYPE_G64 },
80     { "g71", DISK_IMAGE_TYPE_G71 },
81     { "p64", DISK_IMAGE_TYPE_P64 },
82     { "x64", DISK_IMAGE_TYPE_X64 },
83     { NULL, -1 }
84 };
85 
86 
87 /** \brief  Drive unit to attach image to */
88 static int unit_number = 8;
89 /** \brief  Disk image type to create */
90 static int image_type = 1541;
91 
92 /** \brief  GtkEntry containing the disk name */
93 static GtkWidget *disk_name;
94 /** \brief  GtkEntry containing the disk ID */
95 static GtkWidget *disk_id;
96 /** \brief  Set drive type when attaching */
97 static GtkWidget *set_drive_type;
98 
99 
100 /** \brief  Handler for 'response' event of the dialog
101  *
102  * This handler is called when the user clicks a button in the dialog.
103  *
104  * \param[in]   widget      the dialog
105  * \param[in]   response_id response ID
106  * \param[in]   data        extra data (unused)
107  */
on_response(GtkWidget * widget,gint response_id,gpointer data)108 static void on_response(GtkWidget *widget, gint response_id, gpointer data)
109 {
110     gchar *filename;
111     int status = TRUE;
112 
113     switch (response_id) {
114 
115         case GTK_RESPONSE_ACCEPT:
116             filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
117             if (filename != NULL) {
118                 status = create_disk_image(filename);
119             }
120             g_free(filename);
121             if (status) {
122                 /* image creation and attaching was succesful, exit dialog */
123                 gtk_widget_destroy(widget);
124             }
125             break;
126 
127         case GTK_RESPONSE_REJECT:
128             gtk_widget_destroy(widget);
129             break;
130         default:
131             debug_gtk3("warning: unhandled response ID %d\n", response_id);
132             break;
133     }
134 }
135 
136 
137 /** \brief  Handler for the 'changed' event of the image type combo box
138  *
139  * \param[in]   combo   combo box
140  * \param[in]   data    extra event data (unused)
141  *
142  */
on_disk_image_type_changed(GtkComboBox * combo,gpointer data)143 static void on_disk_image_type_changed(GtkComboBox *combo, gpointer data)
144 {
145     GtkTreeModel *model;
146     GtkTreeIter iter;
147 
148     if (gtk_combo_box_get_active(combo) >= 0) {
149         model = gtk_combo_box_get_model(combo);
150         if (gtk_combo_box_get_active_iter(combo, &iter)) {
151             gtk_tree_model_get(model, &iter, 1, &image_type, -1);
152             debug_gtk3("got disk image type %d\n", image_type);
153         }
154     }
155 }
156 
157 
attempt_to_set_drive_type(void)158 static gboolean attempt_to_set_drive_type(void)
159 {
160     if (resources_set_int_sprintf("Drive%dType", image_type, unit_number) < 0) {
161         debug_gtk3("failed to set Drive%dType to %d\n", unit_number, image_type);
162         return FALSE;
163     }
164     return TRUE;
165 }
166 
167 
168 /** \brief  Get the extension for image \a type
169  *
170  * \param[in]   type    image type
171  *
172  * \return  extension or `NULL` when not found
173  */
get_ext_by_image_type(int type)174 static const char *get_ext_by_image_type(int type)
175 {
176     int i = 0;
177 
178     for (i = 0; disk_image_types[i].name != NULL; i++) {
179         if (disk_image_types[i].id == type) {
180             return disk_image_types[i].name;
181         }
182     }
183     return NULL;
184 }
185 
186 
187 /** \brief  Actually create the disk image and attach it
188  *
189  * \param[in]   filename    filename of the new image
190  *
191  * \return  bool
192  */
create_disk_image(const char * filename)193 static gboolean create_disk_image(const char *filename)
194 {
195     char *fname_copy;
196     char name_vice[IMAGE_CONTENTS_NAME_LEN + 1];
197     char id_vice[IMAGE_CONTENTS_ID_LEN + 1];
198     const char *name_gtk3;
199     const char *id_gtk3;
200     char *vdr_text;
201     int status = TRUE;
202 
203     memset(name_vice, 0, IMAGE_CONTENTS_NAME_LEN + 1);
204     memset(id_vice, 0, IMAGE_CONTENTS_ID_LEN + 1);
205     name_gtk3 = gtk_entry_get_text(GTK_ENTRY(disk_name));
206     id_gtk3 = gtk_entry_get_text(GTK_ENTRY(disk_id));
207 
208     /* fix extension of filename */
209     fname_copy = util_add_extension_const(filename,
210                                           get_ext_by_image_type(image_type));
211 
212     /* convert name & ID to PETSCII */
213     if (name_gtk3 != NULL && *name_gtk3 != '\0') {
214         strncpy(name_vice, name_gtk3, IMAGE_CONTENTS_NAME_LEN);
215         charset_petconvstring((unsigned char *)name_vice, 0);
216     }
217     if (id_gtk3 != NULL && *id_gtk3 != '\0') {
218         strncpy(id_vice, id_gtk3, IMAGE_CONTENTS_ID_LEN);
219         charset_petconvstring((unsigned char *)id_vice, 0);
220     } else {
221         strcpy(id_vice, "00");
222     }
223 
224     vdr_text = util_concat(name_vice, ",", id_vice, NULL);
225 #if 0
226     vice_gtk3_message_info("Creating disk image",
227             "Attaching '%s' at unit #%d, type %d, name '%s', ID '%s'\n"
228             "Passing \"%s\" to vdrive",
229             filename, unit_number, image_type, name_gtk3, id_gtk3,
230             vdr_text);
231 #endif
232 
233     /* create image */
234     if (vdrive_internal_create_format_disk_image(fname_copy, vdr_text,
235                 image_type) < 0) {
236         vice_gtk3_message_error("Fail", "Could not create image '%s'",
237                 fname_copy);
238         status = FALSE;
239     } else {
240         /* do we need to attempt to set the proper drive type? */
241         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(set_drive_type))) {
242             /* try to set the proper drive type, but keep going if it fails */
243             if (!attempt_to_set_drive_type()) {
244                 vice_gtk3_message_error("Core error",
245                         "Failed to set drive type to %d\nContinuing.",
246                         image_type);
247             }
248         }
249 
250         /* finally attach the disk image */
251         if (file_system_attach_disk(unit_number, fname_copy) < 0) {
252             vice_gtk3_message_error("fail", "Could not attach image '%s'",
253                     fname_copy);
254             status = FALSE;
255         }
256     }
257 
258     lib_free(fname_copy);
259     lib_free(vdr_text);
260     return status;
261 }
262 
263 
264 
265 
266 /** \brief  Create model for the image type combo box
267  *
268  * \return  model
269  */
create_disk_image_type_model(void)270 static GtkListStore *create_disk_image_type_model(void)
271 {
272     GtkListStore *model;
273     GtkTreeIter iter;
274     int i;
275 
276     model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
277     for (i = 0; disk_image_types[i].name != NULL; i++) {
278         gtk_list_store_append(model, &iter);
279         gtk_list_store_set(model, &iter,
280                 0, disk_image_types[i].name,
281                 1, disk_image_types[i].id,
282                 -1);
283     }
284     return model;
285 }
286 
287 
288 /** \brief  Create combo box with image types
289  *
290  * \return  GtkComboBox
291  */
create_disk_image_type_widget(void)292 static GtkWidget *create_disk_image_type_widget(void)
293 {
294     GtkWidget *combo;
295     GtkListStore *model;
296     GtkCellRenderer *renderer;
297 
298     model = create_disk_image_type_model();
299     combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
300     renderer = gtk_cell_renderer_text_new();
301     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
302     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
303             "text", 0, NULL);
304     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
305 
306     g_signal_connect(combo, "changed", G_CALLBACK(on_disk_image_type_changed),
307             NULL);
308     return combo;
309 }
310 
311 
312 /** \brief  Create the 'extra' widget for the dialog
313  *
314  * \param[in]   parent  parent widget (dialog, unused at the moment)
315  * \param[in]   unit    default unit number (unused)
316  *
317  * \return  GtkGrid
318  */
create_extra_widget(GtkWidget * parent,int unit)319 static GtkWidget *create_extra_widget(GtkWidget *parent, int unit)
320 {
321     GtkWidget *grid;
322     GtkWidget *unit_widget;
323     GtkWidget *type_widget;
324     GtkWidget *label;
325 
326     /* create a grid with some spacing and margins */
327     grid = vice_gtk3_grid_new_spaced(VICE_GTK3_DEFAULT, VICE_GTK3_DEFAULT);
328     g_object_set(grid, "margin-left", 16, "margin-right", 16, NULL);
329 
330     /* add unit selection widget */
331     unit_widget = drive_unit_widget_create(unit, &unit_number, NULL);
332     gtk_widget_set_valign(unit_widget, GTK_ALIGN_CENTER);
333     gtk_grid_attach(GTK_GRID(grid), unit_widget, 0, 0, 1, 1);
334 
335     /* disk name */
336     label = gtk_label_new("Name:");
337     gtk_widget_set_halign(label, GTK_ALIGN_START);
338     disk_name = gtk_entry_new();
339     gtk_entry_set_width_chars(GTK_ENTRY(disk_name), IMAGE_CONTENTS_NAME_LEN);
340     gtk_entry_set_max_length(GTK_ENTRY(disk_name), IMAGE_CONTENTS_NAME_LEN);
341     gtk_grid_attach(GTK_GRID(grid), label, 1, 0, 1, 1);
342     gtk_grid_attach(GTK_GRID(grid), disk_name, 2, 0, 1, 1);
343 
344     /* disk ID */
345     label = gtk_label_new("ID:");
346     gtk_widget_set_halign(label, GTK_ALIGN_START);
347     disk_id = gtk_entry_new();
348     gtk_entry_set_width_chars(GTK_ENTRY(disk_id), IMAGE_CONTENTS_ID_LEN);
349     gtk_entry_set_max_length(GTK_ENTRY(disk_id), IMAGE_CONTENTS_ID_LEN);
350     gtk_grid_attach(GTK_GRID(grid), label, 3, 0, 1, 1);
351     gtk_grid_attach(GTK_GRID(grid), disk_id, 4, 0, 1, 1);
352 
353     /* add image type selection widget */
354     label = gtk_label_new("Type:");
355     type_widget = create_disk_image_type_widget();
356     gtk_grid_attach(GTK_GRID(grid), label, 5, 0, 1, 1);
357     gtk_grid_attach(GTK_GRID(grid), type_widget, 6, 0, 1, 1);
358 
359     /* add 'set drive type for attached image' checkbox */
360     set_drive_type = gtk_check_button_new_with_label(
361             "Set proper drive type when attaching image");
362     /* disable by default */
363     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_drive_type), FALSE);
364     gtk_grid_attach(GTK_GRID(grid), set_drive_type, 0, 1, 4, 1);
365 
366     gtk_widget_show_all(grid);
367     return grid;
368 }
369 
370 
371 /** \brief  Create and show 'attach new disk image' dialog
372  *
373  */
uidiskcreate_dialog_show(GtkWidget * parent,gpointer data)374 void uidiskcreate_dialog_show(GtkWidget *parent, gpointer data)
375 {
376     GtkWidget *dialog;
377     GtkFileFilter *filter;
378     int unit;
379 
380     unit = GPOINTER_TO_INT(data);
381     /* TODO: stuff some UNIT_MIN/UNIT_MAX defines in some file */
382     if (unit < 8 || unit > 11) {
383         unit = 8;
384     }
385     unit_number = unit;
386 
387     dialog = gtk_file_chooser_dialog_new(
388             "Create and attach a new disk image",
389             ui_get_active_window(),
390             GTK_FILE_CHOOSER_ACTION_SAVE,
391             /* buttons */
392             "Save", GTK_RESPONSE_ACCEPT,
393             "Close", GTK_RESPONSE_REJECT,
394             NULL, NULL);
395 
396     gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog),
397             create_extra_widget(dialog, unit));
398 
399     gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
400             TRUE);
401 
402     filter = create_file_chooser_filter(file_chooser_filter_disk, FALSE);
403     gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
404 
405     g_signal_connect(dialog, "response", G_CALLBACK(on_response), NULL);
406 
407     gtk_widget_show(dialog);
408 }
409