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