1 /** \file   uicart.c
2  * \brief   Widget to attach carts
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 
28 #include "vice.h"
29 #include <gtk/gtk.h>
30 #include <string.h>
31 
32 #include "machine.h"
33 #include "resources.h"
34 #include "debug_gtk3.h"
35 #include "basewidgets.h"
36 #include "widgethelpers.h"
37 #include "basedialogs.h"
38 #include "cartimagewidget.h"
39 #include "filechooserhelpers.h"
40 #include "lastdir.h"
41 #include "log.h"
42 #include "openfiledialog.h"
43 #include "savefiledialog.h"
44 #include "cartridge.h"
45 #include "vsync.h"
46 #include "ui.h"
47 #include "uimachinewindow.h"
48 #include "crtpreviewwidget.h"
49 
50 #include "uicart.h"
51 
52 
53 /** \brief  Enum with various cart types, independent from cartridge.h
54  *
55  * The define's in cartridge.h don't provide the values I need, so this will
56  * have to do.
57  */
58 typedef enum ui_cart_type_e {
59 
60     /* C64 cart types */
61     UICART_C64_SMART = 0,
62     UICART_C64_8KB,
63     UICART_C64_16KB,
64     UICART_C64_ULTIMAX,
65     UICART_C64_FREEZER,
66     UICART_C64_GAME,
67     UICART_C64_UTIL,
68 
69     /* VIC20 cart types */
70     UICART_VIC20_SMART,
71     UICART_VIC20_BEHRBONZ,
72     UICART_VIC20_MEGACART,
73     UICART_VIC20_FINALEXP,
74     UICART_VIC20_ULTIMEM,
75     UICART_VIC20_FLASHPLUGIN,
76     UICART_VIC20_GENERIC,
77     UICART_VIC20_ADD_GENERIC,
78 
79     /* Plus4 cart types */
80     UICART_PLUS4_SMART,
81     UICART_PLUS4_NEWROM,
82     UICART_PLUS4_16KB_C0LO,
83     UICART_PLUS4_16KB_C0HI,
84     UICART_PLUS4_16KB_C1LO,
85     UICART_PLUS4_16KB_C1HI,
86     UICART_PLUS4_16KB_C2LO,
87     UICART_PLUS4_16KB_C2HI,
88     UICART_PLUS4_32KB_C0,
89     UICART_PLUS4_32KB_C1,
90     UICART_PLUS4_32KB_C2,
91 
92     /* CBM2 cart types */
93     /*UICART_CBM2_SMART,*/
94     UICART_CBM2_8KB_1000,
95     UICART_CBM2_8KB_2000,
96     UICART_CBM2_16KB_4000,
97     UICART_CBM2_16KB_6000
98 
99 } ui_cart_type_t;
100 
101 
102 /** \brief  Indici of filename patterns
103  */
104 enum {
105     UICART_PATTERN_CRT = 0, /* '*.crt' */
106     UICART_PATTERN_BIN,     /* '*.bin' */
107     UICART_PATTERN_BIN_PRG, /* '*.bin;*.prg' */
108     UICART_PATTERN_ALL      /* '*' */
109 };
110 
111 
112 /** \brief  Simple (text,id) data structure for the cart type model
113  */
114 typedef struct cart_type_list_s {
115     const char *name;
116     int id;
117 } cart_type_list_t;
118 
119 
120 #ifndef SANDBOX_MODE
121 /** \brief  Available C64 cart types
122  *
123  * When the 'type' is freezer, games or utilities, a second combo box will
124  * be populated with cartridges which fall in that category.
125  */
126 static const cart_type_list_t c64_cart_types[] = {
127     { "Smart-attach",   UICART_C64_SMART },
128     { "Raw 8KiB",       UICART_C64_8KB },
129     { "Raw 16iKB",      UICART_C64_16KB },
130     { "Raw Ultimax",    UICART_C64_ULTIMAX },
131     { "Freezer",        UICART_C64_FREEZER },
132     { "Games",          UICART_C64_GAME },
133     { "Utilities",      UICART_C64_UTIL },
134     { NULL,             -1 }
135 };
136 
137 
138 /** \brief  List of VIC-20 'main' cart types
139  *
140  * The generic type will use the generic carts list in #vic20_cart_types_generic
141  */
142 static const cart_type_list_t vic20_cart_types[] = {
143     { "Smart-attach",               UICART_VIC20_SMART },
144     { "Behr Bonz",                  UICART_VIC20_BEHRBONZ },
145     { "Mega Cart",                  UICART_VIC20_MEGACART },
146     { "Final Expansion",            UICART_VIC20_FINALEXP },
147     { "UltiMem",                    UICART_VIC20_ULTIMEM },
148     { "Vic Flash Plugin",           UICART_VIC20_FLASHPLUGIN },
149     { "Generic",                    UICART_VIC20_GENERIC },
150     { "Add to generic cartridge",   UICART_VIC20_ADD_GENERIC },
151     { NULL, -1 }
152 };
153 
154 
155 /** \brief  List of Plus4/C16/C116 cart types
156  */
157 static const cart_type_list_t plus4_cart_types[] = {
158     { "Smart-attach",               UICART_PLUS4_SMART },
159     { "NewROM",                     UICART_PLUS4_NEWROM },
160     { "16KiB C0 Low",               UICART_PLUS4_16KB_C0LO },
161     { "16KiB C0 High",              UICART_PLUS4_16KB_C0HI },
162     { "16KiB C1 Low",               UICART_PLUS4_16KB_C1LO },
163     { "16KiB C1 High",              UICART_PLUS4_16KB_C1HI },
164     { "16KiB C2 Low",               UICART_PLUS4_16KB_C2LO },
165     { "16KiB C2 High",              UICART_PLUS4_16KB_C2HI },
166     { "32KiB C0",                   UICART_PLUS4_32KB_C0 },
167     { "32KiB C1",                   UICART_PLUS4_32KB_C1 },
168     { "32KiB C2",                   UICART_PLUS4_32KB_C2 },
169     { NULL, -1 }
170 };
171 
172 
173 /** \brief  List of CBM-II cart types
174  */
175 static const cart_type_list_t cbm2_cart_types[] = {
176     /*{ "Smart-attach",               UICART_CBM2_SMART },*/
177     { "8KiB at $1000",              UICART_CBM2_8KB_1000 },
178     { "8KiB at $2000",              UICART_CBM2_8KB_2000 },
179     { "16KiB at $4000",             UICART_CBM2_16KB_4000 },
180     { "16KiB at $6000",             UICART_CBM2_16KB_6000 },
181     { NULL, -1 }
182 };
183 
184 
185 /** \brief  List of VIC-20 cart types of the 'generic' variety
186  */
187 static const cart_type_list_t vic20_cart_types_generic[] = {
188     { "Smart-attach cartridge image",   CARTRIDGE_VIC20_DETECT },
189     { "32KiB cartridge at $2000",       CARTRIDGE_VIC20_32KB_2000 },
190     { "4/8/16KiB cartridge at $2000",   CARTRIDGE_VIC20_16KB_2000 },
191     { "4/8/16KiB cartridge at $4000",   CARTRIDGE_VIC20_16KB_4000 },
192     { "4/8/16KiB cartridge at $6000",   CARTRIDGE_VIC20_16KB_6000 },
193     { "4/8KiB cartridge at $A000",      CARTRIDGE_VIC20_8KB_A000 },
194     { "4KiB cartridge at $B000",        CARTRIDGE_VIC20_4KB_B000 },
195     { NULL, -1 }
196 };
197 #endif
198 
199 
200 #ifndef SANDBOX_MODE
201 /** \brief  File filter pattern for CRT images */
202 static const char *pattern_crt[] = { "*.crt", NULL };
203 
204 
205 /** \brief  File filter pattern for raw images */
206 static const char *pattern_bin[] = { "*.bin", NULL };
207 
208 /** \brief  File filter pattern for raw images */
209 static const char *pattern_bin_prg[] = { "*.bin", "*.prg", NULL };
210 #endif
211 
212 
213 #ifndef SANDBOX_MODE
214 /** \brief  File type filters for the dialog
215  */
216 static ui_file_filter_t filters[] = {
217     { "CRT images", pattern_crt },
218     { "Raw images", pattern_bin },
219     { "Raw images", pattern_bin_prg },  /* VIC20 */
220     { "All files", file_chooser_pattern_all },
221     { NULL, NULL }
222 };
223 #endif
224 
225 
226 /** \brief  Last used directory
227  */
228 static gchar *last_dir = NULL;
229 static gchar *last_file = NULL;
230 
231 
232 /* list of cartridge handling functions (to avoid vsid link errors) */
233 
234 /** \brief  Machine-specific cart type detection function pointer */
235 static int  (*crt_detect_func)(const char *filename) = NULL;
236 
237 /** \brief  Machine-specific cart attach function pointer */
238 static int  (*crt_attach_func)(int type, const char *filename) = NULL;
239 
240 /** \brief  Machine-specific cart freeze function pointer */
241 static void (*crt_freeze_func)(void) = NULL;
242 
243 /** \brief  Machine-specific cart detach function pointer */
244 static void (*crt_detach_func)(int type) = NULL;
245 
246 /** \brief  Machine-specific cart info retrieval function pointer */
247 static cartridge_info_t *(*crt_list_func)(void) = NULL;
248 
249 /** \brief  Machine-specific cart set-default function pointer */
250 static void (*crt_set_default_func)(void) = NULL;
251 
252 /** \brief  Machine-specific cart unset-default function pointer */
253 static void (*crt_unset_default_func)(void) = NULL;
254 
255 
256 /* References to widgets used in various event handlers */
257 
258 #ifndef SANDBOX_MODE
259 /** \brief  Reference to the cart dialog */
260 static GtkWidget *cart_dialog = NULL;
261 #endif
262 
263 
264 #ifndef SANDBOX_MODE
265 /** \brief  Reference to the cart dialog */
266 static GtkWidget *cart_type_widget = NULL;
267 #endif
268 
269 
270 #ifndef SANDBOX_MODE
271 /** \brief  Reference to the cart-id widget */
272 static GtkWidget *cart_id_widget = NULL;
273 #endif
274 
275 #ifndef SANDBOX_MODE
276 /** \brief  Reference to the cart content 'preview' widget
277  *
278  * This widget shows the contents of the cart selected in the dialog.
279  */
280 static GtkWidget *cart_preview_widget = NULL;
281 #endif
282 
283 
284 
285 #ifndef SANDBOX_MODE
286 /** \brief  Reference to the cart-set-default widget */
287 static GtkWidget *cart_set_default_widget = NULL;
288 #endif
289 
290 
291 #ifndef SANDBOX_MODE
292 /** \brief  Reference to the cart ID widget */
293 static GtkWidget *cart_id_label = NULL;
294 #endif
295 
296 #ifndef SANDBOX_MODE
297 /** \brief  Reference to the dialog file filter showing only .crt images */
298  GtkFileFilter *flt_crt = NULL;
299 #endif
300 
301 
302 #ifndef SANDBOX_MODE
303 /** \brief  Reference to the dialog file filter showing only .bin images */
304 static GtkFileFilter *flt_bin = NULL;
305 #endif
306 
307 #ifndef SANDBOX_MODE
308 /** \brief  Reference to the dialog file filter showing .bin + .prg images */
309 static GtkFileFilter *flt_bin_prg = NULL;
310 #endif
311 
312 #ifndef SANDBOX_MODE
313 /** \brief  Reference to the dialog file filter showing all files */
314 static GtkFileFilter *flt_all = NULL;
315 #endif
316 
317 /* forward declarations of functions */
318 #ifndef SANDBOX_MODE
319 static GtkListStore *create_cart_id_model(unsigned int flags);
320 static int get_cart_type(void);
321 static int get_cart_id(void);
322 #endif
323 static int attach_cart_image(int type, int id, const char *path);
324 
325 #ifndef SANDBOX_MODE
326 static GtkListStore *create_cart_id_model_vic20(void);
327 #endif
328 
329 
330 /** \brief  Callback for the detach confirm dialog
331  *
332  * If \a result is TRUE, detach all carts.
333  *
334  * \param[in]   dialog  dialog reference (unused)
335  * \param[in]   result  dialog result
336  */
uicart_confirm_detach_callback(GtkDialog * dialog,gboolean result)337 static void uicart_confirm_detach_callback(GtkDialog *dialog, gboolean result)
338 {
339     if (result) {
340         crt_unset_default_func();
341     }
342 }
343 
344 
345 /** \brief  Handler for the "response" event of the dialog
346  *
347  * \param[in]   dialog      dialog
348  * \param[in]   response_id response ID
349  * \param[in]   data        extra event data (unused)
350  */
on_response(GtkWidget * dialog,gint response_id,gpointer data)351 static void on_response(GtkWidget *dialog, gint response_id, gpointer data)
352 {
353     gchar *filename;
354 
355     debug_gtk3("got response ID %d.", response_id);
356     switch (response_id) {
357         case GTK_RESPONSE_DELETE_EVENT:
358             gtk_widget_destroy(dialog);
359             break;
360         case GTK_RESPONSE_ACCEPT:
361             lastdir_update(dialog, &last_dir, &last_file);
362             filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
363             if (filename != NULL) {
364                 gchar *filename_locale = file_chooser_convert_to_locale(filename);
365 
366                 debug_gtk3("attaching '%s'.", filename);
367 #ifndef SANDBOX_MODE
368                 if (!attach_cart_image(get_cart_type(), get_cart_id(),
369                             filename_locale)) {
370 #else
371                 /* FIXME: probably only works for C64/C128 */
372                 if (!attach_cart_image(UICART_C64_SMART, 0,
373                             filename_locale)) {
374 #endif
375                     vice_gtk3_message_error("VICE Error",
376                             "Failed to smart-attach '%s'", filename);
377                 }
378                 g_free(filename);
379                 g_free(filename_locale);
380             }
381 #ifndef SANDBOX_MODE
382             gtk_widget_destroy(dialog);
383 #else
384             gtk_native_dialog_destroy(GTK_NATIVE_DIALOG(dialog));
385 #endif
386             break;
387     }
388 }
389 
390 
391 #ifndef SANDBOX_MODE
392 /** \brief  Set the file filter pattern for the dialog
393  *
394  * \param[in]   pattern UICART_PATTERN_\* enum value
395  */
396 static void set_pattern(int pattern)
397 {
398     GtkFileFilter *filter = NULL;
399 
400     switch (pattern) {
401         case UICART_PATTERN_CRT:
402             filter = flt_crt;
403             break;
404         case UICART_PATTERN_BIN:
405             filter = flt_bin;
406             break;
407         default:
408             filter = flt_all;
409             break;
410     }
411     gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(cart_dialog), filter);
412 }
413 #endif
414 
415 
416 #ifndef SANDBOX_MODE
417 /** \brief  Handler for the "changed" event of the cart type combo box
418  *
419  * \param[in]   combo   cart type combo
420  * \param[in]   data    extra event data (unused)
421  */
422 static void on_cart_type_changed(GtkComboBox *combo, gpointer data)
423 {
424     GtkListStore *id_model;     /* cart 'ID' model */
425     unsigned int mask = ~0;
426     int pattern = UICART_PATTERN_BIN;
427     int crt_type;
428 
429     crt_type = get_cart_type();
430     if (crt_type < 0) {
431         return;
432     }
433 
434     switch (machine_class) {
435         case VICE_MACHINE_C64:      /* fall through */
436         case VICE_MACHINE_C64SC:    /* fall through */
437         case VICE_MACHINE_C128:     /* fall through */
438         case VICE_MACHINE_SCPU64:
439             switch (crt_type) {
440                 case UICART_C64_SMART:
441                     pattern = UICART_PATTERN_CRT;
442                     break;
443                 case UICART_C64_FREEZER:
444                     mask = CARTRIDGE_GROUP_FREEZER;
445                     break;
446                 case UICART_C64_GAME:
447                     mask = CARTRIDGE_GROUP_GAME;
448                     break;
449                 case UICART_C64_UTIL:
450                     mask = CARTRIDGE_GROUP_UTIL;
451                     break;
452                 default:
453                     mask = 0x0;
454                     break;
455             }
456 
457             /* update cart ID model and set it */
458             id_model = create_cart_id_model(mask);
459             gtk_combo_box_set_model(GTK_COMBO_BOX(cart_id_widget), GTK_TREE_MODEL(id_model));
460             gtk_combo_box_set_active(GTK_COMBO_BOX(cart_id_widget), 0);
461             /* only show cart ID list when needed */
462             if ((pattern == UICART_PATTERN_CRT) || (mask == 0x0)) {
463                 gtk_widget_hide(GTK_WIDGET(cart_id_widget));
464                 gtk_widget_hide(GTK_WIDGET(cart_id_label));
465             } else {
466                 gtk_widget_show(GTK_WIDGET(cart_id_widget));
467                 gtk_widget_show(GTK_WIDGET(cart_id_label));
468             }
469 
470             /* update filename pattern */
471             set_pattern(pattern);
472 
473             break;
474         case VICE_MACHINE_VIC20:
475             if ((crt_type == UICART_VIC20_GENERIC) ||
476                 (crt_type == UICART_VIC20_ADD_GENERIC)) {
477                 id_model = create_cart_id_model_vic20();
478                 /* gtk_widget_set_sensitive(cart_id_widget, TRUE); */
479                 gtk_widget_show(GTK_WIDGET(cart_id_widget));
480                 gtk_widget_show(GTK_WIDGET(cart_id_label));
481             } else {
482                 /* empty model */
483                 id_model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
484                 /* gtk_widget_set_sensitive(cart_id_widget, FALSE); */
485                 gtk_widget_hide(GTK_WIDGET(cart_id_widget));
486                 gtk_widget_hide(GTK_WIDGET(cart_id_label));
487             }
488             gtk_combo_box_set_model(GTK_COMBO_BOX(cart_id_widget), GTK_TREE_MODEL(id_model));
489             gtk_combo_box_set_active(GTK_COMBO_BOX(cart_id_widget), 0);
490 
491             break;
492 
493         default:
494             break;
495     }
496 }
497 #endif
498 
499 
500 
501 #ifndef SANDBOX_MODE
502 /** \brief  Get the ID of the model for the 'cart type' combo box
503  *
504  * \return  ID or -1 on error
505  */
506 static int get_cart_type(void)
507 {
508     GtkTreeModel *model;
509     GtkTreeIter iter;
510     GtkComboBox *combo;
511     int crt_type = -1;
512 
513     combo = GTK_COMBO_BOX(cart_type_widget);
514 
515     if (gtk_combo_box_get_active(combo) >= 0) {
516         model = gtk_combo_box_get_model(combo);
517         if (gtk_combo_box_get_active_iter(combo, &iter)) {
518             gtk_tree_model_get(model, &iter, 1, &crt_type, -1);
519         }
520     }
521     return crt_type;
522 }
523 #endif
524 
525 
526 
527 #ifndef SANDBOX_MODE
528 /** \brief  Get the ID of the model for the 'cart ID' combo box
529  *
530  * \return  ID or -1 on error
531  */
532 static int get_cart_id(void)
533 {
534     GtkTreeModel *model;
535     GtkTreeIter iter;
536     GtkComboBox *combo;
537     int crt_id = -1;
538 
539     if (cart_id_widget == NULL) {
540         return crt_id;
541     }
542 
543     combo = GTK_COMBO_BOX(cart_id_widget);
544     if (gtk_combo_box_get_active(combo) >= 0) {
545         model = gtk_combo_box_get_model(combo);
546         if (gtk_combo_box_get_active_iter(combo, &iter)) {
547             gtk_tree_model_get(model, &iter, 1, &crt_id, -1);
548         }
549     }
550     debug_gtk3("got crt_id: %d (%04x)\n", crt_id, (unsigned int)crt_id);
551     return crt_id;
552 }
553 #endif
554 
555 
556 /** \brief  Cart attach handler
557  *
558  * \param[in]   type    cartridge type
559  * \param[in]   id      cartridge ID
560  * \param[in]   path    path to cartrige image
561  *
562  * \return  bool
563  */
564 static int attach_cart_image(int type, int id, const char *path)
565 {
566     switch (machine_class) {
567         case VICE_MACHINE_C64:      /* fall through */
568         case VICE_MACHINE_C64SC:    /* fall through */
569         case VICE_MACHINE_C128:     /* fall through */
570         case VICE_MACHINE_SCPU64:
571             debug_gtk3("attaching cart type %d, cart ID %d.", type, id);
572             switch (type) {
573                 case UICART_C64_SMART:
574                     id = CARTRIDGE_CRT;
575                     break;
576                 case UICART_C64_FREEZER:    /* fall through */
577                 case UICART_C64_GAME:       /* fall through */
578                 case UICART_C64_UTIL:
579                     /* id is correct I think */
580                     break;
581                 default:
582                     debug_gtk3("error: shouldn't get here.");
583                     break;
584             }
585             break;
586 
587         case VICE_MACHINE_VIC20:
588             switch (type) {
589                 case UICART_VIC20_SMART:
590                     id = CARTRIDGE_VIC20_DETECT;
591                     break;
592                 case UICART_VIC20_GENERIC:
593                     /* we also want to select an id for generic type. some day
594                        we need to fix "generic" vs "add to generic" */
595                     /* id = CARTRIDGE_VIC20_GENERIC; */
596                     break;
597                 case UICART_VIC20_BEHRBONZ:
598                     id = CARTRIDGE_VIC20_BEHRBONZ;
599                     break;
600                case UICART_VIC20_MEGACART:
601                     id = CARTRIDGE_VIC20_MEGACART;
602                     break;
603                case UICART_VIC20_FINALEXP:
604                     id = CARTRIDGE_VIC20_FINAL_EXPANSION;
605                     break;
606                case UICART_VIC20_ULTIMEM:
607                     id = CARTRIDGE_VIC20_UM;
608                     break;
609                case UICART_VIC20_FLASHPLUGIN:
610                     id = CARTRIDGE_VIC20_FP;
611                     break;
612                 default:
613                     /* add to generic, id is already set */
614                     debug_gtk3("error: shouldn't get here.");
615                     break;
616             }
617             break;
618 
619         case VICE_MACHINE_PLUS4:
620             switch (type) {
621                 case UICART_PLUS4_SMART:
622                     id = CARTRIDGE_PLUS4_DETECT;
623                     break;
624                 case UICART_PLUS4_NEWROM:
625                     id = CARTRIDGE_PLUS4_NEWROM;
626                     break;
627                 case UICART_PLUS4_16KB_C0LO:
628                     id = CARTRIDGE_PLUS4_16KB_C0LO;
629                     break;
630                 case UICART_PLUS4_16KB_C0HI:
631                     id = CARTRIDGE_PLUS4_16KB_C0HI;
632                     break;
633                 case UICART_PLUS4_16KB_C1LO:
634                     id = CARTRIDGE_PLUS4_16KB_C1LO;
635                     break;
636                 case UICART_PLUS4_16KB_C1HI:
637                     id = CARTRIDGE_PLUS4_16KB_C1HI;
638                     break;
639                 case UICART_PLUS4_16KB_C2LO:
640                     id = CARTRIDGE_PLUS4_16KB_C2LO;
641                     break;
642                 case UICART_PLUS4_16KB_C2HI:
643                     id = CARTRIDGE_PLUS4_16KB_C2HI;
644                     break;
645                 case UICART_PLUS4_32KB_C0:
646                     id = CARTRIDGE_PLUS4_32KB_C0;
647                     break;
648                 case UICART_PLUS4_32KB_C1:
649                     id = CARTRIDGE_PLUS4_32KB_C1;
650                     break;
651                 case UICART_PLUS4_32KB_C2:
652                     id = CARTRIDGE_PLUS4_32KB_C2;
653                     break;
654                 default:
655                     /* oops */
656                     debug_gtk3("error: shouldn't get here.");
657                     break;
658             }
659             break;
660 
661         case VICE_MACHINE_CBM5x0:   /* fall through */
662         case VICE_MACHINE_CBM6x0:
663             switch (type) {
664                 /*case UICART_CBM2_SMART:
665                     return (crt_attach_func(CARTRIDGE_CBM2_DETECT, path) == 0);*/
666                 case UICART_CBM2_8KB_1000:
667                     id = CARTRIDGE_CBM2_8KB_1000;
668                     break;
669                 case UICART_CBM2_8KB_2000:
670                     id = CARTRIDGE_CBM2_8KB_2000;
671                     break;
672                 case UICART_CBM2_16KB_4000:
673                     id = CARTRIDGE_CBM2_16KB_4000;
674                     break;
675                 case UICART_CBM2_16KB_6000:
676                     id = CARTRIDGE_CBM2_16KB_6000;
677                     break;
678                 default:
679                     /* oops */
680                     debug_gtk3("error: shouldn't get here.");
681                     break;
682             }
683             break;
684 
685         default:
686             debug_gtk3("very oops: type = %d, id = %d, path = '%s'.",
687                     type, id, path);
688             return 0;
689             break;
690     }
691 
692     debug_gtk3("attaching cart type %d, cart ID %04x.", type, (unsigned int)id);
693     if ((crt_attach_func(id, path) == 0)) {
694         /* check 'set default' */
695 #ifndef SANDBOX_MODE
696         if ((cart_set_default_widget != NULL)
697                 & (gtk_toggle_button_get_active(
698                         GTK_TOGGLE_BUTTON(cart_set_default_widget)))) {
699             /* set cart as default, there's no return value, so let's assume
700              * this works */
701             debug_gtk3("setting cart with ID %04x as default.", (unsigned int)id);
702             crt_set_default_func();
703         }
704 #endif
705         return 1;
706     }
707     return 0;
708 }
709 
710 
711 
712 #ifndef SANDBOX_MODE
713 /** \brief  Create model for the 'cart type' combo box
714  *
715  * This depends on the `machine_class`, so for some machines, this may return
716  * an empty (useless) model
717  *
718  * \return  model
719  */
720 static GtkListStore *create_cart_type_model(void)
721 {
722     GtkListStore *model;
723     GtkTreeIter iter;
724     const cart_type_list_t *types;
725     int i;
726 
727     model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
728     switch (machine_class) {
729         case VICE_MACHINE_C64:      /* fall through */
730         case VICE_MACHINE_C64SC:    /* fall through */
731         case VICE_MACHINE_C128:     /* fall through */
732         case VICE_MACHINE_SCPU64:
733             types = c64_cart_types;
734             break;
735         case VICE_MACHINE_VIC20:
736             types = vic20_cart_types;
737             break;
738         case VICE_MACHINE_PLUS4:
739             types = plus4_cart_types;
740             break;
741         case VICE_MACHINE_CBM5x0:
742         case VICE_MACHINE_CBM6x0:
743             types = cbm2_cart_types;
744             break;
745         default:
746             return model;
747     }
748 
749     for (i = 0; types[i].name != NULL; i++) {
750         gtk_list_store_append(model, &iter);
751         gtk_list_store_set(model, &iter, 0, types[i].name, 1, types[i].id, -1);
752     }
753     return model;
754 }
755 #endif
756 
757 
758 #ifndef SANDBOX_MODE
759 /** \brief  Create a list of cartridges, filtered with \a flags
760  *
761  * Only valid for c64/c128/scpu
762  *
763  * \param[in]   flags   flags determining what cart types to use
764  *
765  * \return  Three-column list store (name, crtid, flags)
766  */
767 static GtkListStore *create_cart_id_model(unsigned int flags)
768 {
769     GtkListStore *model;
770     GtkTreeIter iter;
771     cartridge_info_t *list;
772     int i;
773 
774     model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT);
775 
776     if (crt_list_func != NULL) {
777         list = crt_list_func();
778     } else {
779         return model;
780     }
781 
782 
783     for (i = 0; list[i].name != NULL; i++) {
784         if (list[i].flags & flags) {
785             gtk_list_store_append(model, &iter);
786             gtk_list_store_set(model, &iter,
787                     0, list[i].name,        /* cart name */
788                     1, list[i].crtid,       /* cart ID */
789                     2, list[i].flags,       /* cart flags */
790                     -1);
791         }
792     }
793     return model;
794 }
795 #endif
796 
797 
798 #ifndef SANDBOX_MODE
799 /** \brief  Create a list of cartridges for VIC-20
800  *
801  * Only valid for VIC-20
802  *
803  * \return  Two-column list store (name, id)
804  */
805 static GtkListStore *create_cart_id_model_vic20(void)
806 {
807     GtkListStore *model;
808     GtkTreeIter iter;
809     int i;
810 
811     model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
812 
813     for (i = 0; vic20_cart_types_generic[i].name != NULL; i++) {
814         gtk_list_store_append(model, &iter);
815         gtk_list_store_set(model, &iter,
816                 0, vic20_cart_types_generic[i].name,    /* cart name */
817                 1, vic20_cart_types_generic[i].id,      /* cart ID */
818                 -1);
819     }
820     return model;
821 }
822 #endif
823 
824 
825 #ifndef SANDBOX_MODE
826 /** \brief  Create combo box with main cartridge types
827  *
828  * \return  GtkComboBox
829  */
830 static GtkWidget *create_cart_type_combo_box(void)
831 {
832     GtkWidget *combo;
833     GtkListStore *model;
834     GtkCellRenderer *renderer;
835 
836     model = create_cart_type_model();
837     if (model == NULL) {
838         return gtk_combo_box_new();
839     }
840     combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
841     g_object_unref(model);
842 
843     renderer = gtk_cell_renderer_text_new();
844     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
845     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
846             "text", 0, NULL);
847 
848     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
849 
850     g_signal_connect(combo, "changed", G_CALLBACK(on_cart_type_changed), NULL);
851     return combo;
852 }
853 #endif
854 
855 
856 #ifndef SANDBOX_MODE
857 /** \brief  Create combo box with cartridges with adhere to \a mask
858  *
859  * \param[in]   mask    bitmask to filter cartridges
860  *
861  * \return  GtkComboBox
862  *
863  * \note    Only for x64/x64sc/xscp64/x128
864  */
865 static GtkWidget *create_cart_id_combo_box(unsigned int mask)
866 {
867     GtkWidget *combo;
868     GtkListStore *model;
869     GtkCellRenderer *renderer;
870 
871     model = create_cart_id_model(mask);
872     if (model == NULL) {
873         return gtk_combo_box_new();
874     }
875     combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
876     g_object_unref(model);
877 
878     renderer = gtk_cell_renderer_text_new();
879     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
880     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
881             "text", 0, NULL);
882 
883     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
884     return combo;
885 }
886 #endif
887 
888 #ifndef SANDBOX_MODE
889 /** \brief  Create combo box with generic VIC-20 cartridges
890  *
891  * \return  GtkComboBox
892  */
893 static GtkWidget *create_cart_id_combo_box_vic20(void)
894 {
895     GtkWidget *combo;
896     GtkListStore *model;
897     GtkCellRenderer *renderer;
898 
899     model = create_cart_id_model_vic20();
900     if (model == NULL) {
901         return gtk_combo_box_new();
902     }
903     combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
904     g_object_unref(model);
905 
906     renderer = gtk_cell_renderer_text_new();
907     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
908     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
909             "text", 0, NULL);
910 
911     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
912     return combo;
913 }
914 #endif
915 
916 
917 #ifndef SANDBOX_MODE
918 /** \brief  Create the 'extra' widget for the dialog
919  *
920  * \return  GtkGrid
921  */
922 static GtkWidget *create_extra_widget(void)
923 {
924     GtkWidget *grid;
925     GtkWidget *label;
926 
927     grid = gtk_grid_new();
928     gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
929     gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
930 
931     label = gtk_label_new("cartridge type");
932     gtk_widget_set_halign(label, GTK_ALIGN_START);
933     cart_type_widget = create_cart_type_combo_box();
934     gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
935     gtk_grid_attach(GTK_GRID(grid), cart_type_widget, 1, 0, 1, 1);
936 
937     /* create "Set cartridge as default" check button */
938     switch (machine_class) {
939         case VICE_MACHINE_C64:      /* fall through */
940         case VICE_MACHINE_C64SC:    /* fall through */
941         case VICE_MACHINE_SCPU64:   /* fall through */
942         case VICE_MACHINE_VIC20:
943             cart_set_default_widget = gtk_check_button_new_with_label(
944                     "Set cartridge as default");
945             gtk_toggle_button_set_active(
946                     GTK_TOGGLE_BUTTON(cart_set_default_widget), FALSE);
947 
948             gtk_grid_attach(GTK_GRID(grid), cart_set_default_widget, 0, 1, 4, 1);
949             break;
950         default:
951             /* Set cart as default is not supported for the current machine */
952             break;
953     }
954 
955     /* only for c64/c128 */
956     switch (machine_class) {
957         case VICE_MACHINE_C64:      /* fall through */
958         case VICE_MACHINE_C64SC:    /* fall through */
959         case VICE_MACHINE_C128:     /* fall through */
960         case VICE_MACHINE_SCPU64:
961 
962             cart_id_label = gtk_label_new("cartridge ID");
963             gtk_widget_set_halign(cart_id_label, GTK_ALIGN_START);
964             cart_id_widget = create_cart_id_combo_box(0x0);
965             gtk_grid_attach(GTK_GRID(grid), cart_id_label, 2, 0, 1, 1);
966             gtk_grid_attach(GTK_GRID(grid), cart_id_widget, 3, 0, 1, 1);
967             break;
968         case VICE_MACHINE_VIC20:
969             cart_id_label = gtk_label_new("cartridge class");
970             gtk_widget_set_halign(cart_id_label, GTK_ALIGN_START);
971             cart_id_widget = create_cart_id_combo_box_vic20();
972             gtk_grid_attach(GTK_GRID(grid), cart_id_label, 2, 0, 1, 1);
973             gtk_grid_attach(GTK_GRID(grid), cart_id_widget, 3, 0, 1, 1);
974             break;
975 
976         default:
977             break;
978     }
979 
980     gtk_widget_show_all(grid);
981     return grid;
982 }
983 #endif
984 
985 
986 #ifndef SANDBOX_MODE
987 /** \brief  Create the 'preview' widget for the dialog
988  *
989  * \return  GtkGrid
990  */
991 static GtkWidget *create_preview_widget(void)
992 {
993     if ((machine_class != VICE_MACHINE_C64)
994             && (machine_class != VICE_MACHINE_C64SC)
995             && (machine_class != VICE_MACHINE_C128)) {
996         GtkWidget *grid = NULL;
997         GtkWidget *label;
998         grid = gtk_grid_new();
999         gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
1000         gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
1001 
1002         label = gtk_label_new(NULL);
1003         gtk_label_set_markup(GTK_LABEL(label), "<b>Cartridge info</b>");
1004         gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
1005 
1006         label = gtk_label_new("Error: groepaz was here!");
1007         g_object_set(label, "margin-left", 16, NULL);
1008         gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
1009 
1010         gtk_widget_show_all(grid);
1011         return grid;
1012     } else {
1013         return crt_preview_widget_create();
1014     }
1015 
1016 }
1017 #endif
1018 
1019 #ifndef SANDBOX_MODE
1020 /** \brief  Update the 'preview' widget for the dialog
1021  *
1022  * \param[in,out]   file_chooser    GtkFileChooser instance
1023  * \param[in]       data            extra event data (unused)
1024  *
1025  * \return  GtkGrid
1026  */
1027 static void  update_preview(GtkFileChooser *file_chooser, gpointer data)
1028 {
1029     gchar *path = NULL;
1030 
1031     debug_gtk3("update_preview");
1032     path = gtk_file_chooser_get_filename(file_chooser);
1033     if (path != NULL) {
1034         gchar *path_locale = file_chooser_convert_to_locale(path);
1035         crt_preview_widget_update(path_locale);
1036         g_free(path);
1037         g_free(path_locale);
1038     }
1039 }
1040 #endif
1041 
1042 
1043 /** \brief  Set function to get a list of cartridges
1044  *
1045  * \param[in]   func    list function
1046  */
1047 void ui_cart_set_list_func(cartridge_info_t *(*func)(void))
1048 {
1049     crt_list_func = func;
1050 }
1051 
1052 
1053 
1054 
1055 /** \brief  Set function to detect a cartridge's type
1056  *
1057  * Appears to be CBM2/Plus4 only
1058  *
1059  * \param[in]   func    detect function
1060  */
1061 void ui_cart_set_detect_func(int (*func)(const char *))
1062 {
1063     crt_detect_func = func;
1064 }
1065 
1066 
1067 /** \brief  Set function to attach a cartridge image
1068  *
1069  * \param[in]   func    attach function
1070  */
1071 void ui_cart_set_attach_func(int (*func)(int, const char *))
1072 {
1073     crt_attach_func = func;
1074 }
1075 
1076 
1077 /** \brief  Set function to trigger a cartridge freeze-button click
1078  *
1079  * \param[in]   func    freeze function
1080  */
1081 void ui_cart_set_freeze_func(void (*func)(void))
1082 {
1083     crt_freeze_func = func;
1084 }
1085 
1086 
1087 /** \brief  Set function to detach a/all cartridges
1088  *
1089  * \param[in]   func    freeze function
1090  */
1091 void ui_cart_set_detach_func(void (*func)(int))
1092 {
1093     crt_detach_func = func;
1094 }
1095 
1096 
1097 /** \brief  Set function to set active cart as default
1098  *
1099  * \param[in]   func    default func
1100  */
1101 void ui_cart_set_set_default_func(void (*func)(void))
1102 {
1103     crt_set_default_func = func;
1104 }
1105 
1106 /** \brief  Set function to set active cart as default
1107  *
1108  * \param[in]   func    default func
1109  */
1110 void ui_cart_set_unset_default_func(void (*func)(void))
1111 {
1112     crt_unset_default_func = func;
1113 }
1114 
1115 
1116 /** \brief  Trigger cartridge freeze
1117  *
1118  * Called from the menu
1119  *
1120  *
1121  * \return  TRUE
1122  */
1123 gboolean ui_cart_trigger_freeze(void)
1124 {
1125     if (crt_freeze_func != NULL) {
1126         debug_gtk3("triggering cart freeze.");
1127         crt_freeze_func();
1128     }
1129     return TRUE;
1130 }
1131 
1132 
1133 /** \brief  Detach all cartridge images
1134  *
1135  * TODO: The question about removing the default enabled cartridge doesn't
1136  *       work for carts not on "slot 1", these seem to their own "enabled"
1137  *       resources and do not use the CartridgeType/CartridgeFile resources.
1138  *
1139  * \return  TRUE
1140  */
1141 gboolean ui_cart_detach(void)
1142 {
1143     int cartid = CARTRIDGE_NONE;
1144 
1145     if (crt_detach_func != NULL) {
1146 
1147         /* determine if one of the attached cartridges is set as default
1148            cartridge, and if so ask if it should be removed from default also */
1149 
1150         /* first check if the set_default and unset_default functions exist. some
1151            emulators do not have this feature, in this case we just use detach */
1152         if (crt_set_default_func != NULL) {
1153             if (crt_unset_default_func != NULL) {
1154                 /* when both functions exist, check if the default is actually set */
1155                 /* FIXME: perhaps we should have a dedicated function for getting the,
1156                           id of slot1 default cart, however this will work for a start */
1157                 resources_get_int("CartridgeType", &cartid);
1158                 if (cartid != CARTRIDGE_NONE) {
1159                     /* default is set, ask to remove it */
1160                     vice_gtk3_message_confirm(
1161                             uicart_confirm_detach_callback,
1162                             "Detach cartridge",
1163                             "You're detaching the default cartridge.\n\n"
1164                             "Would you also like to unregister this cartridge"
1165                             " as the default cartridge?");
1166 #if 0
1167                     if (result) {
1168                         crt_unset_default_func();
1169                     }
1170 #endif
1171                 }
1172                 /* FIXME: the above will only check/ask for "slot1" cartridges. other
1173                           cartridges have seperate "enable" resources which would
1174                           have to be checked individually. perhaps make a dedicated
1175                           function for this later */
1176             } else {
1177                 log_message(LOG_DEFAULT, "FIXME: cartridge_set_default exists, but cartridge_unset_default is not implemented");
1178             }
1179         }
1180 
1181         debug_gtk3("detaching all cartridges.");
1182         crt_detach_func(-1);    /* detach all cartridges */
1183     }
1184     return TRUE;
1185 }
1186 
1187 
1188 #ifndef SANDBOX_MODE
1189 /** \brief  Pop up the cart-attach dialog
1190  *
1191  * \param[in]   widget  parent widget (unused)
1192  * \param[in]   data    extra event data (unused)
1193  *
1194  * \return  TRUE
1195  */
1196 gboolean ui_cart_show_dialog(GtkWidget *widget, gpointer data)
1197 {
1198     GtkWidget *dialog;
1199 
1200     dialog = gtk_file_chooser_dialog_new(
1201             "Attach a cartridge image",
1202             ui_get_active_window(),
1203             GTK_FILE_CHOOSER_ACTION_OPEN,
1204             /* buttons */
1205             "Attach", GTK_RESPONSE_ACCEPT,
1206             "Close", GTK_RESPONSE_DELETE_EVENT,
1207             NULL, NULL);
1208 
1209     /* set modal so mouse-grab doesn't get triggered */
1210     gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1211 
1212     /* set last directory */
1213     lastdir_set(dialog, &last_dir, &last_file);
1214 
1215     /* add extra widget */
1216     gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog),
1217             create_extra_widget());
1218 
1219     /* add preview widget */
1220     cart_preview_widget = create_preview_widget();
1221     gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog),
1222             cart_preview_widget);
1223     gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog), FALSE);
1224 
1225     /* add filters */
1226     flt_crt = create_file_chooser_filter(filters[UICART_PATTERN_CRT], FALSE);
1227     flt_bin = create_file_chooser_filter(filters[UICART_PATTERN_BIN], FALSE);
1228     flt_bin_prg = create_file_chooser_filter(filters[UICART_PATTERN_BIN_PRG], FALSE);
1229     flt_all = create_file_chooser_filter(filters[UICART_PATTERN_ALL], TRUE);
1230 
1231     switch (machine_class) {
1232         case VICE_MACHINE_C64:      /* fall through */
1233         case VICE_MACHINE_C64SC:    /* fall through */
1234         case VICE_MACHINE_C128:     /* fall through */
1235         case VICE_MACHINE_SCPU64:
1236             gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_crt);
1237             gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_bin);
1238             gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_all);
1239             break;
1240         case VICE_MACHINE_VIC20:
1241             gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_bin_prg);
1242             gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_all);
1243             break;
1244         default:
1245             break;
1246     }
1247 
1248     cart_dialog = dialog;
1249 
1250     g_signal_connect(dialog, "response", G_CALLBACK(on_response), NULL);
1251     g_signal_connect(dialog, "update-preview", G_CALLBACK(update_preview), NULL);
1252 
1253     /* those should be hidden by default */
1254     if (cart_id_label) {
1255         gtk_widget_hide(cart_id_label);
1256     }
1257     if (cart_id_widget) {
1258         gtk_widget_hide(cart_id_widget);
1259     }
1260 
1261     gtk_widget_show(dialog);
1262     return TRUE;
1263 }
1264 
1265 #else
1266 
1267 gboolean ui_cart_show_dialog(GtkWidget *widget, gpointer data)
1268 {
1269     GtkFileChooserNative *dialog;
1270 
1271     dialog = gtk_file_chooser_native_new(
1272             "Attach a cartridge image",
1273             ui_get_active_window(),
1274             GTK_FILE_CHOOSER_ACTION_OPEN,
1275             /* buttons */
1276             NULL, NULL);
1277 
1278     g_signal_connect(dialog, "response",
1279             G_CALLBACK(on_response),
1280             GINT_TO_POINTER(0));
1281 
1282     gtk_native_dialog_show(GTK_NATIVE_DIALOG(dialog));
1283     return TRUE;
1284 }
1285 #endif
1286 
1287 
1288 /** \brief  Clean up the last directory string
1289  */
1290 void ui_cart_shutdown(void)
1291 {
1292     lastdir_shutdown(&last_dir, &last_file);
1293 }
1294