1 /** \file   ui.c
2  * \brief   Native GTK3 UI stuff
3  *
4  * \author  Marco van den Heuvel <blackystardust68@yahoo.com>
5  * \author  Bas Wassink <b.wassink@ziggo.nl>
6  * \author  Marcus Sutton <loggedoubt@gmail.com>
7  */
8 
9 /*
10  * This file is part of VICE, the Versatile Commodore Emulator.
11  * See README for copyright notice.
12  *
13  *  This program is free software; you can redistribute it and/or modify
14  *  it under the terms of the GNU General Public License as published by
15  *  the Free Software Foundation; either version 2 of the License, or
16  *  (at your option) any later version.
17  *
18  *  This program is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License
24  *  along with this program; if not, write to the Free Software
25  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
26  *  02111-1307  USA.
27  *
28  */
29 
30 #include "vice.h"
31 
32 #include <stdio.h>
33 #include <string.h>
34 #include <gtk/gtk.h>
35 
36 #ifdef UNIX_COMPILE
37 #include <unistd.h>
38 #endif
39 
40 #include "debug_gtk3.h"
41 
42 #include "archdep.h"
43 
44 #include "autostart.h"
45 #include "cmdline.h"
46 #include "drive.h"
47 #include "interrupt.h"
48 #include "kbd.h"
49 #include "lib.h"
50 #include "log.h"
51 #include "machine.h"
52 #include "lightpen.h"
53 #include "resources.h"
54 #include "util.h"
55 #include "videoarch.h"
56 #include "vsync.h"
57 #include "vsyncapi.h"
58 
59 #include "basedialogs.h"
60 #include "uiapi.h"
61 #include "uicommands.h"
62 #include "uimenu.h"
63 #include "uisettings.h"
64 #include "uistatusbar.h"
65 #include "jamdialog.h"
66 #include "uicart.h"
67 #include "uidiskattach.h"
68 #include "uismartattach.h"
69 #include "uitapeattach.h"
70 #include "uimachinewindow.h"
71 #include "mixerwidget.h"
72 #include "uidata.h"
73 #include "archdep.h"
74 
75 #include "ui.h"
76 
77 
78 /* Forward declarations of static functions */
79 
80 static int set_save_resources_on_exit(int val, void *param);
81 static int set_confirm_on_exit(int val, void *param);
82 static int set_window_height(int val, void *param);
83 static int set_window_width(int val, void *param);
84 static int set_window_xpos(int val, void *param);
85 static int set_window_ypos(int val, void *param);
86 static int set_start_minimized(int val, void *param);
87 static int set_native_monitor(int val, void *param);
88 static int set_fullscreen_state(int val, void *param);
89 static void ui_toggle_warp(void);
90 
91 
92 
93 /*****************************************************************************
94  *                  Defines, enums, type declarations                        *
95  ****************************************************************************/
96 
97 /** \brief  Struct holding basic UI rescources
98  */
99 typedef struct ui_resources_s {
100 
101     int save_resources_on_exit; /**< SaveResourcesOnExit (bool) */
102     int confirm_on_exit;        /**< ConfirmOnExit (bool) */
103 
104     int start_minimized;        /**< StartMinimized (bool) */
105 
106     int use_native_monitor;     /**< NativeMonitor (bool) */
107 
108 #if 0
109     int depth;
110 #endif
111 
112     video_canvas_t *canvas[NUM_WINDOWS];
113     GtkWidget *window_widget[NUM_WINDOWS]; /**< the toplevel GtkWidget (Window) */
114     int window_width[NUM_WINDOWS];
115     int window_height[NUM_WINDOWS];
116     int window_xpos[NUM_WINDOWS];
117     int window_ypos[NUM_WINDOWS];
118 
119 } ui_resource_t;
120 
121 
122 /** \brief  Collection of UI resources
123  *
124  * This needs to stay here, to allow the command line and resources initializers
125  * to reference the UI resources.
126  */
127 static ui_resource_t ui_resources;
128 
129 
130 static int fullscreen_enabled = 0;
131 
132 
133 /** \brief  Row numbers of the various widgets packed in a main GtkWindow
134  */
135 enum {
136     ROW_MENU_BAR = 0,   /**< application menu bar */
137     ROW_DISPLAY,        /**< emulated display */
138     ROW_STATUS_BAR,     /**< status bar */
139     ROW_CRT_CONTROLS,   /**< CRT control widgets */
140     ROW_MIXER_CONTROLS  /**< mixer control widgets */
141 };
142 
143 
144 /** \brief  Default hotkeys for the UI not connected to a menu item
145  */
146 static kbd_gtk3_hotkey_t default_hotkeys[] = {
147     /* Alt+P: toggle pause */
148     { GDK_KEY_p, VICE_MOD_MASK, (void *)ui_toggle_pause },
149     /* Alt+W: toggle warp mode */
150     { GDK_KEY_w, VICE_MOD_MASK, ui_toggle_warp },
151     /* Alt+Shift+P: Advance frame (only when paused)
152      *
153      * XXX: seems GDK_KEY_*P* is required here, otherwise the key press isn't
154      *      recognized (only tested on Win10)
155      */
156     { GDK_KEY_P, VICE_MOD_MASK|GDK_SHIFT_MASK, (void *)ui_advance_frame },
157 
158     /* Arnie */
159     { 0, 0, NULL }
160 };
161 
162 
163 
164 
165 /*****************************************************************************
166  *                              Static data                                  *
167  ****************************************************************************/
168 
169 /** \brief  String type resources list
170  */
171 #if 0
172 static const resource_string_t resources_string[] = {
173     RESOURCE_STRING_LIST_END
174 };
175 #endif
176 
177 
178 /** \brief  Boolean resources shared between windows
179  */
180 static const resource_int_t resources_int_shared[] = {
181     { "SaveResourcesOnExit", 0, RES_EVENT_NO, NULL,
182         &ui_resources.save_resources_on_exit, set_save_resources_on_exit, NULL },
183     { "ConfirmOnExit", 1, RES_EVENT_NO, NULL,
184         &ui_resources.confirm_on_exit, set_confirm_on_exit, NULL },
185 
186     { "StartMinimized", 0, RES_EVENT_NO, NULL,
187         &ui_resources.start_minimized, set_start_minimized, NULL },
188 
189     { "NativeMonitor", 0, RES_EVENT_NO, NULL,
190         &ui_resources.use_native_monitor, set_native_monitor, NULL },
191 
192     { "FullscreenEnable", 0, RES_EVENT_NO, NULL,
193         &fullscreen_enabled, set_fullscreen_state, NULL },
194 
195 
196     RESOURCE_INT_LIST_END
197 };
198 
199 
200 /** \brief  Window size and position resources for the primary window
201  *
202  * These are used by all emulators.
203  */
204 static const resource_int_t resources_int_primary_window[] = {
205     { "Window0Height", 0, RES_EVENT_NO, NULL,
206         &(ui_resources.window_height[PRIMARY_WINDOW]), set_window_height,
207         (void*)PRIMARY_WINDOW },
208     { "Window0Width", 0, RES_EVENT_NO, NULL,
209         &(ui_resources.window_width[PRIMARY_WINDOW]), set_window_width,
210         (void*)PRIMARY_WINDOW },
211     { "Window0Xpos", 0, RES_EVENT_NO, NULL,
212         &(ui_resources.window_xpos[PRIMARY_WINDOW]), set_window_xpos,
213         (void*)PRIMARY_WINDOW },
214     { "Window0Ypos", 0, RES_EVENT_NO, NULL,
215         &(ui_resources.window_ypos[PRIMARY_WINDOW]), set_window_ypos,
216         (void*)PRIMARY_WINDOW },
217 
218     RESOURCE_INT_LIST_END
219 };
220 
221 
222 /** \brief  Window size and position resources list for the secondary window
223  *
224  * These are only used by x128's VDC window.
225  */
226 static const resource_int_t resources_int_secondary_window[] = {
227     { "Window1Height", 0, RES_EVENT_NO, NULL,
228         &(ui_resources.window_height[SECONDARY_WINDOW]), set_window_height,
229         (void*)SECONDARY_WINDOW },
230     { "Window1Width", 0, RES_EVENT_NO, NULL,
231         &(ui_resources.window_width[SECONDARY_WINDOW]), set_window_width,
232         (void*)SECONDARY_WINDOW },
233     { "Window1Xpos", 0, RES_EVENT_NO, NULL,
234         &(ui_resources.window_xpos[SECONDARY_WINDOW]), set_window_xpos,
235         (void*)SECONDARY_WINDOW },
236     { "Window1Ypos", 0, RES_EVENT_NO, NULL,
237         &(ui_resources.window_ypos[SECONDARY_WINDOW]), set_window_ypos,
238         (void*)SECONDARY_WINDOW },
239 
240     RESOURCE_INT_LIST_END
241 };
242 
243 
244 /** \brief  Command line options shared between emu's, include VSID
245  */
246 static const cmdline_option_t cmdline_options_common[] =
247 {
248     { "-confirmonexit", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
249         NULL, NULL, "ConfirmOnExit", (void *)1,
250         NULL, "Confirm quitting VICE" },
251     { "+confirmonexit", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
252         NULL, NULL, "ConfirmOnExit", (void *)0,
253         NULL, "Do not confirm quitting VICE" },
254     { "-saveres", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
255         NULL, NULL, "SaveResourcesOnExit", (void *)1,
256         NULL, "Save settings on exit" },
257     { "+saveres", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
258         NULL, NULL, "SaveResourcesOnExit", (void *)0,
259         NULL, "Do not save settings on exit" },
260     { "-minimized", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
261         NULL, NULL, "StartMinimized", (void *)1,
262         NULL, "Start VICE minimized" },
263     { "+minimized", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
264         NULL, NULL, "StartMinimized", (void *)0,
265         NULL, "Do not start VICE minimized" },
266     { "-native-monitor", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
267         NULL, NULL, "NativeMonitor", (void *)1,
268         NULL, "Use native monitor on OS terminal" },
269     { "+native-monitor", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
270         NULL, NULL, "NativeMonitor", (void *)0,
271         NULL, "Use VICE Gtk3 monitor terminal" },
272     { "-fullscreen", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
273         NULL, NULL, "FullscreenEnable", (void*)1,
274         NULL, "Enable fullscreen" },
275     { "+fullscreen", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
276         NULL, NULL, "FullscreenEnable", (void*)0,
277         NULL, "Disable fullscreen" },
278 
279     CMDLINE_LIST_END
280 };
281 
282 
283 /** \brief  Drag-n-drop 'target' types
284  */
285 enum {
286     DT_TEXT,        /**< simple text (text/plain) */
287     DT_URI,         /**< haven't seen this one get triggered (yet) */
288     DT_URI_LIST     /**< used by Windows Explorer / macOS Finder */
289 };
290 
291 
292 /** \brief  List of drag targets for the drag-n-drop event handler
293  *
294  * It would appear different OS'es/WM's pass dropped files using various
295  * mime-types.
296  */
297 static GtkTargetEntry drag_targets[] = {
298     { "text/plain",     0, DT_TEXT },   /* we get this on at least my Linux
299                                            box with Mate */
300     { "text/uri",       0, DT_URI },
301     { "text/uri-list",  0, DT_URI_LIST }    /* we get this using Windows
302                                                Explorer or macOS Finder */
303 };
304 
305 
306 /** \brief  Flag indicating pause mode
307  */
308 static int is_paused = 0;
309 
310 /** \brief  Index of the most recently focused main window
311  */
312 static int active_win_index = -1;
313 
314 /** \brief  Flag indicating whether we're supposed to be in fullscreen
315  */
316 static int is_fullscreen = 0;
317 
318 /** \brief  Flag inidicating whether fullscreen mode shows the decorations
319  */
320 static int fullscreen_has_decorations = 0;
321 
322 /** \brief  Function to handle files dropped on a main window
323  */
324 static int (*handle_dropped_files_func)(const char *) = NULL;
325 
326 /** \brief  Function to help create a main window
327  */
328 static void (*create_window_func)(video_canvas_t *) = NULL;
329 
330 /** \brief  Function to identify a canvas from its video chip
331  */
332 static int (*identify_canvas_func)(video_canvas_t *) = NULL;
333 
334 /** \brief  Function to help create a CRT controls widget
335  */
336 static GtkWidget *(*create_controls_widget_func)(int) = NULL;
337 
338 
339 /******************************************************************************
340  *                              Event handlers                                *
341  *****************************************************************************/
342 
343 
344 /** \brief  Handler for the 'drag-drop' event of the GtkWindow(s)
345  *
346  * Can be used to filter certain drop targets or altering the data before
347  * triggering the 'drag-drop-received' event. Currently just returns TRUE
348  *
349  * \param[in]   widget  widget triggering the event
350  * \param[in]   context gtk drag context
351  * \param[in]   x       x position of drag event
352  * \param[in]   y       y position of drag event
353  * \param[in]   time    (I don't have a clue)
354  * \param[in]   data    extra event data (unused)
355  */
on_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer data)356 static gboolean on_drag_drop(
357         GtkWidget *widget,
358         GdkDragContext *context,
359         gint x,
360         gint y,
361         guint time,
362         gpointer data)
363 {
364     debug_gtk3("called.");
365     return TRUE;
366 }
367 
368 
369 /** \brief  Handler for the 'drag-data-received' event
370  *
371  * Autostarts an image/prg when valid
372  *
373  * \param[in]   widget      widget triggering the event (unused)
374  * \param[in]   context     drag context (unused)
375  * \param[in]   x           probably X-coordinate in the drop target?
376  * \param[in]   y           probablt Y-coordinate in the drop target?
377  * \param[in]   data        dragged data
378  * \param[in]   info        int declared in the targets array (unclear)
379  * \param[in]   time        no idea
380  *
381  * \todo    Once this works properly, remove a lot of debugging calls, perhaps
382  *          changing a few into log calls.
383  */
on_drag_data_received(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * data,guint info,guint time)384 static void on_drag_data_received(
385         GtkWidget *widget,
386         GdkDragContext *context,
387         int x,
388         int y,
389         GtkSelectionData *data,
390         guint info,
391         guint time)
392 {
393     gchar **uris;
394     gchar *filename = NULL;
395     gchar **files = NULL;
396     guchar *text = NULL;
397     int i;
398 
399     debug_gtk3("got drag-data, info = %u:", info);
400 
401     switch (info) {
402 
403         case DT_URI_LIST:
404             /*
405              * This branch appears to be taken on both Windows and macOS.
406              */
407 
408             /* got possible list of URI's */
409             uris = gtk_selection_data_get_uris(data);
410             if (uris != NULL) {
411                 /* dump URI's on stdout */
412                 debug_gtk3("got URI's:");
413                 for (i = 0; uris[i] != NULL; i++) {
414 
415                     debug_gtk3("URI: '%s'\n", uris[i]);
416                     filename = g_filename_from_uri(uris[i], NULL, NULL);
417                     debug_gtk3("filename: '%s'.", filename);
418                     if (filename != NULL) {
419                         g_free(filename);
420                     }
421                 }
422 
423                 /* use the first/only entry as the autostart file
424                  *
425                  * XXX: perhaps add any additional files to the fliplist
426                  *      if Dxx?
427                  */
428                 if (uris[0] != NULL) {
429                     filename = g_filename_from_uri(uris[0], NULL, NULL);
430                 } else {
431                     filename = NULL;
432                 }
433 
434                 g_strfreev(uris);
435             }
436             break;
437 
438         case DT_TEXT:
439             /*
440              * this branch appears to be taken on both Gtk and Qt based WM's
441              * on Linux
442              */
443 
444 
445             /* text will contain a newline separated list of 'file://' URIs,
446              * and a trailing newline */
447             text = gtk_selection_data_get_text(data);
448             /* remove trailing whitespace */
449             g_strchomp((gchar *)text);
450 
451             debug_gtk3("Got data as text: '%s'.", text);
452             files = g_strsplit((const gchar *)text, "\n", -1);
453             g_free(text);
454 
455             for (i = 0; files[i] != NULL; i++) {
456 #ifdef HAVE_DEBUG_GTK3UI
457                 gchar *tmp = g_filename_from_uri(files[i], NULL, NULL);
458 #endif
459                 debug_gtk3("URI: '%s', filename: '%s'.",
460                         files[i], tmp);
461             }
462             /* now grab the first file */
463             filename = g_filename_from_uri(files[0], NULL, NULL);
464             g_strfreev(files);
465 
466             debug_gtk3("got filename '%s'.", filename);
467             break;
468 
469         default:
470             debug_gtk3("Warning: unhandled d'n'd target %u.", info);
471             filename = NULL;
472             break;
473     }
474 
475     /* can we attempt autostart? */
476     if (filename != NULL) {
477         if (machine_class != VICE_MACHINE_VSID) {
478 
479             debug_gtk3("Attempting to autostart '%s'.", filename);
480             if (autostart_autodetect(filename, NULL, 0, AUTOSTART_MODE_RUN) != 0) {
481                 debug_gtk3("failed.");
482             } else {
483                 debug_gtk3("OK!");
484             }
485         } else {
486             /* try to open SID file, reports error itself */
487             if (handle_dropped_files_func != NULL) {
488                 handle_dropped_files_func(filename);
489             }
490         }
491         g_free(filename);
492     }
493 }
494 
495 
set_fullscreen_state(int val,void * param)496 static int set_fullscreen_state(int val, void *param)
497 {
498     debug_gtk3("called with %d.", val);
499     fullscreen_enabled = val;
500     return 0;
501 }
502 
503 
504 
505 /** \brief  Get the most recently focused toplevel window
506  *
507  * \return  pointer to a toplevel window, or NULL
508  *
509  * \note    Not an event handler, needs to be moved
510  */
ui_get_active_window(void)511 GtkWindow *ui_get_active_window(void)
512 {
513     GtkWindow *window = NULL;
514     GList *tlist = gtk_window_list_toplevels();
515     GList *list = tlist;
516 
517     /* Find the window that has the toplevel focus. */
518     while (list != NULL) {
519         if (gtk_window_has_toplevel_focus(list->data)) {
520             window = list->data;
521             break;
522         }
523         list = list->next;
524     }
525     g_list_free(tlist);
526 
527     /* If no window has the toplevel focus, then fall back
528      * to the most recently focused main window.
529      */
530     if (window == NULL
531             && active_win_index >= 0 && active_win_index < NUM_WINDOWS) {
532         window = GTK_WINDOW(ui_resources.window_widget[active_win_index]);
533     }
534 
535     /* If "window" still is NULL, it probably means
536      * that no windows have been created yet.
537      */
538     return window;
539 }
540 
541 
542 /** \brief  Get video canvas of active window
543  *
544  * \return  current active video canvas, or NULL
545  */
ui_get_active_canvas(void)546 video_canvas_t *ui_get_active_canvas(void)
547 {
548     if (active_win_index < 0) {
549         /* If we end up here it probably means no main window has
550          * been created yet. */
551         return NULL;
552     }
553     return ui_resources.canvas[active_win_index];
554 }
555 
556 
557 /** \brief  Get a window's index
558  *
559  * \param[in]   widget      window to get the index of
560  *
561  * \return  window index, or -1 if not a main window
562  */
ui_get_window_index(GtkWidget * widget)563 static int ui_get_window_index(GtkWidget *widget)
564 {
565     if (widget == NULL) {
566         return -1;
567     } else if (widget == ui_resources.window_widget[PRIMARY_WINDOW]) {
568         return PRIMARY_WINDOW;
569     } else if (widget == ui_resources.window_widget[SECONDARY_WINDOW]) {
570         return SECONDARY_WINDOW;
571     } else {
572         return -1;
573     }
574 }
575 
576 /** \brief  Handler for the "focus-in-event" of a main window
577  *
578  * \param[in]   widget      window triggering the event
579  * \param[in]   event       window focus details
580  * \param[in]   user_data   extra data for the event (ignored)
581  *
582  * \return  FALSE to continue processing
583  *
584  * \note    We only use this for canvas-window-specific stuff like
585  *          fullscreen mode.
586  */
on_focus_in_event(GtkWidget * widget,GdkEventFocus * event,gpointer user_data)587 static gboolean on_focus_in_event(GtkWidget *widget, GdkEventFocus *event,
588                                   gpointer user_data)
589 {
590     int index = ui_get_window_index(widget);
591 
592     /* printf("ui.c:on_focus_in_event\n"); */
593 
594     ui_mouse_grab_pointer();
595 
596     if (index < 0) {
597         /* We should never end up here. */
598         log_error(LOG_ERR, "focus-in-event: window not found\n");
599         archdep_vice_exit(1);
600     }
601 
602     if (event->in == TRUE) {
603         /* fprintf(stderr, "window %d: focus-in\n", index); */
604         active_win_index = index;
605     }
606 
607     return FALSE;
608 }
609 
610 /** \brief  Handler for the "focus-out-event" of a main window
611  *
612  * \param[in]   widget      window triggering the event
613  * \param[in]   event       window focus details
614  * \param[in]   user_data   extra data for the event (ignored)
615  *
616  * \return  FALSE to continue processing
617  *
618  * \note    We only use this for canvas-window-specific stuff like
619  *          fullscreen mode.
620  */
on_focus_out_event(GtkWidget * widget,GdkEventFocus * event,gpointer user_data)621 static gboolean on_focus_out_event(GtkWidget *widget, GdkEventFocus *event,
622                                   gpointer user_data)
623 {
624     /* printf("ui.c:on_focus_out_event\n"); */
625 
626     ui_mouse_ungrab_pointer();
627 
628     return FALSE;
629 }
630 
631 /** \brief  Create an icon by loading it from the vice.gresource file
632  *
633  * \return  Standard C= icon ripped from the internet (but at least scalable)
634  *          Which ofcourse sucks on Windows for some reason, *sigh*
635  */
get_default_icon(void)636 static GdkPixbuf *get_default_icon(void)
637 {
638     return uidata_get_pixbuf("CBM_Logo.svg");
639 }
640 
641 
642 /** \brief Show or hide the decorations of the active main window as needed
643  */
ui_update_fullscreen_decorations(void)644 static void ui_update_fullscreen_decorations(void)
645 {
646     GtkWidget *window, *grid, *menu_bar, *crt_grid, *mixer_grid, *status_bar;
647     int has_decorations;
648 
649     /* FIXME: this function does not work properly for vsid and should never
650      * get called by it, but at least on Macs it can get called if the user
651      * clicks the fullscreen button in the main vsid window.
652      */
653     if (active_win_index < 0 || machine_class == VICE_MACHINE_VSID) {
654         return;
655     }
656 
657     has_decorations = (!is_fullscreen) || fullscreen_has_decorations;
658     window = ui_resources.window_widget[active_win_index];
659     grid = gtk_bin_get_child(GTK_BIN(window));
660     menu_bar = gtk_grid_get_child_at(GTK_GRID(grid), 0, ROW_MENU_BAR);
661     crt_grid = gtk_grid_get_child_at(GTK_GRID(grid), 0, ROW_CRT_CONTROLS);
662     mixer_grid = gtk_grid_get_child_at(GTK_GRID(grid), 0, ROW_MIXER_CONTROLS);
663     status_bar = gtk_grid_get_child_at(GTK_GRID(grid), 0, ROW_STATUS_BAR);
664 
665     if (has_decorations) {
666         gtk_widget_show(menu_bar);
667         if (ui_statusbar_crt_controls_enabled(window)) {
668             gtk_widget_show(crt_grid);
669         }
670         if (ui_statusbar_mixer_controls_enabled(window)) {
671             gtk_widget_show(mixer_grid);
672         }
673         gtk_widget_show(status_bar);
674     } else {
675         gtk_widget_hide(menu_bar);
676         gtk_widget_hide(crt_grid);
677         gtk_widget_hide(mixer_grid);
678         gtk_widget_hide(status_bar);
679     }
680 }
681 
682 /** \brief  Handler for the "window-state-event" of a main window
683  *
684  * \param[in]   widget      window triggering the event
685  * \param[in]   event       window state details
686  * \param[in]   user_data   extra data for the event (ignored)
687  *
688  * \return  FALSE to continue processing
689  */
on_window_state_event(GtkWidget * widget,GdkEventWindowState * event,gpointer user_data)690 static gboolean on_window_state_event(GtkWidget *widget,
691                                       GdkEventWindowState *event,
692                                       gpointer user_data)
693 {
694     GdkWindowState win_state = event->new_window_state;
695     int index = ui_get_window_index(widget);
696 
697     if (index < 0) {
698         /* We should never end up here. */
699         log_error(LOG_ERR, "window-state-event: window not found\n");
700         archdep_vice_exit(1);
701     }
702 
703     if (win_state & GDK_WINDOW_STATE_FULLSCREEN) {
704         if (!is_fullscreen) {
705             is_fullscreen = 1;
706             ui_update_fullscreen_decorations();
707         }
708     } else {
709         if (is_fullscreen) {
710             is_fullscreen = 0;
711             ui_update_fullscreen_decorations();
712         }
713     }
714 
715     return FALSE;
716 }
717 
718 
719 /** \brief  Checks if we're in fullscreen mode
720  *
721  * \return  nonzero if we're in fullscreen mode
722  */
ui_is_fullscreen(void)723 int ui_is_fullscreen(void)
724 {
725     return is_fullscreen;
726 }
727 
728 /** \brief  Updates UI in response to the simulated machine screen
729  *          changing its dimensions or aspect ratio
730  */
ui_trigger_resize(void)731 void ui_trigger_resize(void)
732 {
733     int i;
734     for (i = 0; i < NUM_WINDOWS; ++i) {
735         if (ui_resources.canvas[i]) {
736             video_canvas_adjust_aspect_ratio(ui_resources.canvas[i]);
737         }
738         if (ui_resources.window_widget[i]) {
739             gtk_widget_queue_resize(ui_resources.window_widget[i]);
740         }
741     }
742 }
743 
744 /** \brief  Toggles fullscreen mode in reaction to user request
745  *
746  * \param[in]   widget      the widget that sent the callback (ignored)
747  * \param[in]   user_data   extra data for the callback (ignored)
748  */
ui_fullscreen_callback(GtkWidget * widget,gpointer user_data)749 void ui_fullscreen_callback(GtkWidget *widget, gpointer user_data)
750 {
751     GtkWindow *window;
752 
753     if (active_win_index < 0) {
754         return;
755     }
756 
757     window = GTK_WINDOW(ui_resources.window_widget[active_win_index]);
758     is_fullscreen = !is_fullscreen;
759 
760     if (is_fullscreen) {
761         gtk_window_fullscreen(window);
762     } else {
763         gtk_window_unfullscreen(window);
764     }
765 
766     ui_update_fullscreen_decorations();
767 }
768 
769 /** \brief Toggles fullscreen window decorations in response to user request
770  *
771  * \param[in]   widget      the widget that sent the callback (ignored)
772  * \param[in]   user_data   extra data for the callback (ignored)
773  */
ui_fullscreen_decorations_callback(GtkWidget * widget,gpointer user_data)774 void ui_fullscreen_decorations_callback(GtkWidget *widget, gpointer user_data)
775 {
776     fullscreen_has_decorations = !fullscreen_has_decorations;
777     ui_update_fullscreen_decorations();
778 }
779 
780 
781 /** \brief  Get a window-spec array index from \a param
782  *
783  * Also performs a bounds check and returns -1 on boundary violation.
784  *
785  * \param[in]   param   extra param passed to a setter
786  *
787  * \return  index in array or -1 on error
788  */
window_index_from_param(void * param)789 static int window_index_from_param(void *param)
790 {
791     int index = vice_ptr_to_int(param);
792     return (index >= 0 && index < NUM_WINDOWS) ? index : -1;
793 }
794 
795 
796 /*
797  * Resource getters/setters
798  */
799 
800 
801 /** \brief  Set SaveResourcesOnExit resource
802  *
803  * \param[in]   val     new value
804  * \param[in]   param   extra param (ignored)
805  *
806  * \return 0
807  */
set_save_resources_on_exit(int val,void * param)808 static int set_save_resources_on_exit(int val, void *param)
809 {
810     ui_resources.save_resources_on_exit = val ? 1 : 0;
811     return 0;
812 }
813 
814 
815 /** \brief  Set ConfirmOnExit resource (bool)
816  *
817  * \param[in]   val     new value
818  * \param[in]   param   extra param (ignored)
819  *
820  * \return 0
821  */
set_confirm_on_exit(int val,void * param)822 static int set_confirm_on_exit(int val, void *param)
823 {
824     ui_resources.confirm_on_exit = val ? 1 : 0;
825     return 0;
826 }
827 
828 
829 /** \brief  Set StartMinimized resource (bool)
830  *
831  * \param[in]   val     0: start normal 1: start minimized
832  * \param[in]   param   extra param (ignored)
833  *
834  * \return 0
835  */
set_start_minimized(int val,void * param)836 static int set_start_minimized(int val, void *param)
837 {
838     ui_resources.start_minimized = val ? 1 : 0;
839     return 0;
840 }
841 
842 
843 /** \brief  Set NativeMonitor resource (bool)
844  *
845  * \param[in]   val     new value
846  * \param[in]   param   extra param (ignored)
847  *
848  * \return 0
849  */
set_native_monitor(int val,void * param)850 static int set_native_monitor(int val, void *param)
851 {
852     /* FIXME: setting this to 1 should probably fail if either stdin or stdout
853               is not a terminal. */
854 #if 0
855     if (!isatty(stdin) || !isatty(stdout)) {
856         return -1;
857     }
858 #endif
859     ui_resources.use_native_monitor = val ? 1 : 0;
860     return 0;
861 }
862 
863 
864 
865 /** \brief  Set Window[X]Width resource (int)
866  *
867  * \param[in]   val     width in pixels
868  * \param[in]   param   window index
869  *
870  * \return 0
871  */
set_window_width(int val,void * param)872 static int set_window_width(int val, void *param)
873 {
874     int index = window_index_from_param(param);
875     if (index < 0 || val < 0) {
876         return -1;
877     }
878     ui_resources.window_width[index] = val;
879     return 0;
880 }
881 
882 
883 /** \brief  Set Window[X]Height resource (int)
884  *
885  * \param[in]   val     height in pixels
886  * \param[in]   param   window index
887  *
888  * \return 0
889  */
set_window_height(int val,void * param)890 static int set_window_height(int val, void *param)
891 {
892     int index = window_index_from_param(param);
893     if (index < 0 || val < 0) {
894         return -1;
895     }
896     ui_resources.window_height[index] = val;
897     return 0;
898 }
899 
900 
901 /** \brief  Set Window[X]Xpos resource (int)
902  *
903  * \param[in]   val     x-pos in pixels
904  * \param[in]   param   window index
905  *
906  * \return 0
907  */
set_window_xpos(int val,void * param)908 static int set_window_xpos(int val, void *param)
909 {
910     int index = window_index_from_param(param);
911     if (index < 0 || val < 0) {
912         return -1;
913     }
914     ui_resources.window_xpos[index] = val;
915     return 0;
916 }
917 
918 
919 /** \brief  Set Window[X]Ypos resource (int)
920  *
921  * \param[in]   val     y-pos in pixels
922  * \param[in]   param   window index
923  *
924  * \return 0
925  */
set_window_ypos(int val,void * param)926 static int set_window_ypos(int val, void *param)
927 {
928     int index = window_index_from_param(param);
929     if (index < 0 || val < 0) {
930         return -1;
931     }
932     ui_resources.window_ypos[index] = val;
933     return 0;
934 }
935 
936 
937 /*
938  * Function pointer setters
939  */
940 
941 
942 /** \brief  Set function to handle files dropped on a main window
943  *
944  * \param[in]   func    handle dropped files function
945  */
ui_set_handle_dropped_files_func(int (* func)(const char *))946 void ui_set_handle_dropped_files_func(int (*func)(const char *))
947 {
948     handle_dropped_files_func = func;
949 }
950 
951 
952 /** \brief  Set function to help create the main window(s)
953  *
954  * \param[in]   func    create window function
955  */
ui_set_create_window_func(void (* func)(video_canvas_t *))956 void ui_set_create_window_func(void (*func)(video_canvas_t *))
957 {
958     create_window_func = func;
959 }
960 
961 
962 /** \brief  Set function to identify a canvas from its video chip
963  *
964  * \param[in]   func    identify canvas function
965  */
ui_set_identify_canvas_func(int (* func)(video_canvas_t *))966 void ui_set_identify_canvas_func(int (*func)(video_canvas_t *))
967 {
968     identify_canvas_func = func;
969 }
970 
971 
972 /** \brief  Set function to help create the CRT controls widget(s)
973  *
974  * \param[in]   func    create CRT controls widget function
975  */
ui_set_create_controls_widget_func(GtkWidget * (* func)(int))976 void ui_set_create_controls_widget_func(GtkWidget *(*func)(int))
977 {
978     create_controls_widget_func = func;
979 }
980 
981 
982 /** \brief  Handler for the "destroy" event of \a widget
983  *
984  * Looks like debug code. But better keep it here to debug the warnings about
985  * GtkEventBox'es getting destroyed prematurely.
986  *
987  * \param[in]   widget  widget
988  */
on_window_grid_destroy(GtkWidget * widget,gpointer data)989 static void on_window_grid_destroy(GtkWidget *widget, gpointer data)
990 {
991     debug_gtk3("destroy triggered on %p.", (void *)widget);
992 }
993 
994 
995 /** \brief  Handler for window 'configure' events
996  *
997  * Triggered when a window is moved or changes size. Used currently to update
998  * the Window[01]* resources to allow restoring window size and position on
999  * emulator start.
1000  *
1001  * \param[in]   widget  widget triggering the event
1002  * \param[in]   event   event reference
1003  * \param[in]   data    extra event data (used to pass window index)
1004  *
1005  * \return  bool
1006  */
on_window_configure_event(GtkWidget * widget,GdkEvent * event,gpointer data)1007 static gboolean on_window_configure_event(GtkWidget *widget,
1008                                           GdkEvent *event,
1009                                           gpointer data)
1010 {
1011     if (event->type == GDK_CONFIGURE) {
1012         GdkEventConfigure *cfg = (GdkEventConfigure *)event;
1013 
1014         /* determine Window index */
1015         int windex = GPOINTER_TO_INT(data);
1016 
1017 #if 0
1018         debug_gtk3("updating window #%d coords and size to (%d,%d)/(%d*%d)"
1019                 " in resources.",
1020                 0, cfg->x, cfg->y, cfg->width, cfg->height);
1021 #endif
1022         /* set resources, ignore failures */
1023         resources_set_int_sprintf("Window%dWidth", cfg->width, windex);
1024         resources_set_int_sprintf("Window%dHeight", cfg->height, windex);
1025         resources_set_int_sprintf("Window%dXpos", cfg->x, windex);
1026         resources_set_int_sprintf("Window%dYpos", cfg->y, windex);
1027     }
1028     return FALSE;
1029 }
1030 
1031 
1032 /** \brief  Create a toplevel window to represent a video canvas
1033  *
1034  * This function takes a video canvas structure and builds the widgets
1035  * that will represent that canvas in the UI as a whole. In the machine
1036  * emulators, the GtkDrawingArea that represents the actual screen backed
1037  * by the canvas will be entered into canvas->drawing_area.
1038  *
1039  * While it creates the widgets, it does not make them visible. The
1040  * video canvas routines are expected to do any last-minute processing
1041  * or preparation, and then call ui_display_main_window() when ready.
1042  *
1043  * \param[in]   canvas  the video_canvas_s to initialize
1044  *
1045  * \warning The code that calls this apparently creates the VDC window
1046  *          for x128 before the VIC window (primary) - this is
1047  *          probably done so the VIC window ends up being on top of
1048  *          the VDC window. however, we better call some "move window
1049  *          to front" function instead, and create the windows
1050  *          starting with the primary one.
1051  */
ui_create_main_window(video_canvas_t * canvas)1052 void ui_create_main_window(video_canvas_t *canvas)
1053 {
1054     GtkWidget *new_window;
1055     GtkWidget *grid;
1056     GtkWidget *status_bar;
1057     int target_window;
1058 
1059     GtkWidget *crt_controls;
1060     GtkWidget *mixer_controls;
1061 
1062     GdkPixbuf *icon;
1063 
1064     int xpos = -1;
1065     int ypos = -1;
1066     int width = 0;
1067     int height = 0;
1068 
1069     gchar title[256];
1070 
1071     int full = 0;
1072 
1073     new_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1074     /* this needs to be here to make the menus with accelerators work */
1075     ui_menu_init_accelerators(new_window);
1076 
1077     /* set a default C= icon for now */
1078     icon = get_default_icon();
1079     if (icon != NULL) {
1080         gtk_window_set_icon(GTK_WINDOW(new_window), icon);
1081     }
1082 
1083     /* set title */
1084     g_snprintf(title, 256, "VICE (%s)", machine_get_name());
1085     gtk_window_set_title(GTK_WINDOW(new_window), title);
1086 
1087     grid = gtk_grid_new();
1088     g_signal_connect(grid, "destroy", G_CALLBACK(on_window_grid_destroy), NULL);
1089     gtk_container_add(GTK_CONTAINER(new_window), grid);
1090     gtk_orientable_set_orientation(GTK_ORIENTABLE(grid), GTK_ORIENTATION_VERTICAL);
1091     canvas->grid = grid;
1092 
1093     if (create_window_func != NULL) {
1094         create_window_func(canvas);
1095     }
1096 
1097     target_window = -1;
1098     if (identify_canvas_func != NULL) {
1099         /* Identify the window as the PRIMARY_WINDOW or SECONDARY_WINDOW. */
1100         target_window = identify_canvas_func(canvas);
1101     }
1102     if (target_window < 0) {
1103         log_error(LOG_ERR, "ui_create_main_window: canvas not identified!\n");
1104         archdep_vice_exit(1);
1105     }
1106     if (ui_resources.window_widget[target_window] != NULL) {
1107         log_error(LOG_ERR, "ui_create_main_window: existing window recreated??\n");
1108         archdep_vice_exit(1);
1109     }
1110 
1111     /* add status bar */
1112     status_bar = ui_statusbar_create();
1113     gtk_widget_show_all(status_bar);
1114     gtk_widget_set_no_show_all(status_bar, TRUE);
1115 
1116     gtk_container_add(GTK_CONTAINER(grid), status_bar);
1117 
1118     /* add CRT controls */
1119     crt_controls = NULL;
1120 
1121     if (machine_class != VICE_MACHINE_VSID) {
1122 
1123         if (create_controls_widget_func != NULL) {
1124             crt_controls = create_controls_widget_func(target_window);
1125         }
1126         if (crt_controls != NULL) {
1127             gtk_widget_hide(crt_controls);
1128             gtk_container_add(GTK_CONTAINER(grid), crt_controls);
1129             gtk_widget_set_no_show_all(crt_controls, TRUE);
1130         }
1131     }
1132 
1133     if (machine_class != VICE_MACHINE_VSID) {
1134 
1135         /* add sound mixer controls */
1136         mixer_controls = mixer_widget_create(TRUE, GTK_ALIGN_END);
1137         gtk_widget_hide(mixer_controls);
1138         gtk_container_add(GTK_CONTAINER(grid), mixer_controls);
1139         gtk_widget_set_no_show_all(mixer_controls, TRUE);
1140     }
1141 
1142     g_signal_connect(new_window, "focus-in-event",
1143                      G_CALLBACK(on_focus_in_event), NULL);
1144     g_signal_connect(new_window, "focus-out-event",
1145                      G_CALLBACK(on_focus_out_event), NULL);
1146     g_signal_connect(new_window, "window-state-event",
1147                      G_CALLBACK(on_window_state_event), NULL);
1148     g_signal_connect(new_window, "delete-event",
1149                      G_CALLBACK(ui_main_window_delete_event), NULL);
1150     g_signal_connect(new_window, "destroy",
1151                      G_CALLBACK(ui_main_window_destroy_callback), NULL);
1152     /* can probably use the `user_data` to pass window index */
1153     g_signal_connect(new_window, "configure-event",
1154                      G_CALLBACK(on_window_configure_event),
1155                      GINT_TO_POINTER(target_window));
1156     /*
1157      * Set up drag-n-drop handling for files
1158      */
1159     gtk_drag_dest_set(
1160             new_window,
1161             GTK_DEST_DEFAULT_ALL,
1162             drag_targets,
1163             (int)(sizeof drag_targets / sizeof drag_targets[0]),
1164             GDK_ACTION_COPY);
1165     g_signal_connect(new_window, "drag-data-received",
1166                      G_CALLBACK(on_drag_data_received), NULL);
1167     g_signal_connect(new_window, "drag-drop",
1168                      G_CALLBACK(on_drag_drop), NULL);
1169     if (ui_resources.start_minimized) {
1170         gtk_window_iconify(GTK_WINDOW(new_window));
1171     }
1172 
1173     ui_resources.canvas[target_window] = canvas;
1174     ui_resources.window_widget[target_window] = new_window;
1175 
1176     canvas->window_index = target_window;
1177 
1178     /* gtk_window_set_title(GTK_WINDOW(new_window), canvas->viewport->title); */
1179     ui_display_speed(100.0f, 0.0f, 0); /* initial update of the window status bar */
1180 
1181     /* connect keyboard handlers */
1182     kbd_connect_handlers(new_window, NULL);
1183 
1184     /* Add default hotkeys that don't have a menu item */
1185     if (!kbd_hotkey_add_list(default_hotkeys)) {
1186         debug_gtk3("adding hotkeys failed, see the log for details.");
1187     }
1188 
1189     /*
1190      * Try to restore windows position and size
1191      */
1192 
1193     if (resources_get_int_sprintf("Window%dXpos", &xpos, target_window) < 0) {
1194         log_error(LOG_ERR, "No for Window%dXpos", target_window);
1195     }
1196     resources_get_int_sprintf("Window%dYpos", &ypos, target_window);
1197     resources_get_int_sprintf("Window%dwidth", &width, target_window);
1198     resources_get_int_sprintf("Window%dheight", &height, target_window);
1199 
1200     debug_gtk3("X: %d, Y: %d, W: %d, H: %d", xpos, ypos, width, height);
1201     if (xpos < 0 || ypos < 0 || width <= 0 || height <= 0) {
1202         /* def. not legal */
1203         debug_gtk3("shit ain't legal!");
1204     } else {
1205         gtk_window_move(GTK_WINDOW(new_window), xpos, ypos);
1206         gtk_window_resize(GTK_WINDOW(new_window), width, height);
1207     }
1208 
1209     if (resources_get_int("FullscreenEnable", &full) < 0) {
1210         debug_gtk3("failed to get FullscreenEnabled resource.");
1211     } else {
1212         if (full) {
1213             gtk_window_fullscreen(GTK_WINDOW(new_window));
1214         } else {
1215             gtk_window_unfullscreen(GTK_WINDOW(new_window));
1216         }
1217     }
1218 }
1219 
1220 
1221 /** \brief  Makes a main window visible once it's been initialized
1222  *
1223  * \param[in]   index   which window to display
1224  *
1225  * \sa      ui_resources_s::window_widget
1226  */
ui_display_main_window(int index)1227 void ui_display_main_window(int index)
1228 {
1229     if (ui_resources.window_widget[index]) {
1230         /* Normally this would show everything in the window,
1231          * including hidden status bar displays, but we've
1232          * disabled secondary displays in the status bar code with
1233          * gtk_widget_set_no_show_all(). */
1234         gtk_widget_show_all(ui_resources.window_widget[index]);
1235         active_win_index = index;
1236     }
1237 }
1238 
1239 /** \brief  Destroy a main window
1240  *
1241  * \param[in]   index   which window to destroy
1242  *
1243  * \sa      ui_resources_s::window_widget
1244  */
ui_destroy_main_window(int index)1245 void ui_destroy_main_window(int index)
1246 {
1247     if (ui_resources.window_widget[index]) {
1248         gtk_widget_destroy(ui_resources.window_widget[index]);
1249     }
1250 }
1251 
1252 
1253 /** \brief  Initialize command line options (generic)
1254  *
1255  * \return  0 on success, -1 on failure
1256  */
ui_cmdline_options_init(void)1257 int ui_cmdline_options_init(void)
1258 {
1259     /* seems complete to me -- compyx */
1260 #if 0
1261     INCOMPLETE_IMPLEMENTATION();
1262 #endif
1263     return cmdline_register_options(cmdline_options_common);
1264 }
1265 
1266 
1267 /** \brief  Display a generic file chooser dialog
1268  *
1269  * \param[in]   format  format string for the dialog's title
1270  *
1271  * \return  a copy of the chosen file's name; free it with lib_free()
1272  *
1273  * \note    This is currently only called by event_playback_attach_image()
1274  *
1275  * \warning This function is unimplemented and will intentionally crash
1276  *          VICE if it is called.
1277  */
ui_get_file(const char * format,...)1278 char *ui_get_file(const char *format, ...)
1279 {
1280     /*
1281      * Also not called when trying to play back events, at least, I've never
1282      * seen this called.
1283      */
1284     NOT_IMPLEMENTED();
1285     return NULL;
1286 }
1287 
1288 
1289 /** \brief  Initialize Gtk3/GLib
1290  *
1291  * \param[in]   argc    pointer to main()'s argc
1292  * \param[in]   argv    main()'s argv
1293  *
1294  * \return  0 on success, -1 on failure
1295  */
ui_init(int * argc,char ** argv)1296 int ui_init(int *argc, char **argv)
1297 {
1298 
1299     GSettings *settings;
1300     GVariant *variant;
1301 
1302 #if 0
1303     INCOMPLETE_IMPLEMENTATION();
1304 #endif
1305     gtk_init(argc, &argv);
1306 
1307     kbd_hotkey_init();
1308 
1309     if (!uidata_init()) {
1310         log_error(LOG_ERR,
1311                 "failed to initialize GResource data, don't expected much"
1312                 " when it comes to icons, fonts or logos.");
1313     }
1314 
1315     debug_gtk3("Registering CBM font.");
1316     if (!archdep_register_cbmfont()) {
1317         debug_gtk3("failed, continuing");
1318         log_error(LOG_ERR, "failed to register CBM font.");
1319     }
1320 
1321     /*
1322      * Sort directories before files in GtkFileChooser
1323      *
1324      * Perhaps turn this into a resource when people start complaining? Though
1325      * personally I'm used to having directories sorted before files.
1326      */
1327     settings = g_settings_new("org.gtk.Settings.FileChooser");
1328     variant = g_variant_new("b", TRUE);
1329     g_settings_set_value(settings, "sort-directories-first", variant);
1330 
1331     ui_statusbar_init();
1332     return 0;
1333 }
1334 
1335 
1336 /** \brief  Finish initialization after loading the resources
1337  *
1338  * \note    This function exists for compatibility with other UIs.
1339  *
1340  * \return  0 on success, -1 on failure
1341  *
1342  * \sa      ui_init_finalize()
1343  */
ui_init_finish(void)1344 int ui_init_finish(void)
1345 {
1346     return 0;
1347 }
1348 
1349 
1350 /** \brief  Finalize initialization after creating the main window(s)
1351  *
1352  * \note    This function exists for compatibility with other UIs,
1353  *          but could perhaps be used to activate fullscreen from the
1354  *          command-line or saved settings file (as it is in WinVICE.)
1355  *
1356  * \return  0 on success, -1 on failure
1357  *
1358  * \sa      ui_init_finish()
1359  */
ui_init_finalize(void)1360 int ui_init_finalize(void)
1361 {
1362     return 0;
1363 }
1364 
1365 
1366 /** \brief  Display a dialog box in response to a CPU jam
1367  *
1368  * \param[in]   format  format string for the message to display
1369  *
1370  * \return  the action the user selected in response to the jam
1371  */
ui_jam_dialog(const char * format,...)1372 ui_jam_action_t ui_jam_dialog(const char *format, ...)
1373 {
1374     va_list args;
1375     char *buffer;
1376     int result;
1377 
1378     va_start(args, format);
1379     buffer = lib_mvsprintf(format, args);
1380     va_end(args);
1381 
1382     ui_set_ignore_mouse_hide(TRUE);
1383 
1384     /* XXX: this sucks */
1385     result = jam_dialog(ui_resources.window_widget[PRIMARY_WINDOW], buffer);
1386     lib_free(buffer);
1387 
1388     ui_set_ignore_mouse_hide(FALSE);
1389 
1390     return result;
1391 }
1392 
1393 
1394 /** \brief  Initialize resources related to the UI in general
1395  *
1396  * \return  0 on success, -1 on failure
1397  */
ui_resources_init(void)1398 int ui_resources_init(void)
1399 {
1400     int i;
1401 
1402     /* initialize command int/bool resources */
1403     if (resources_register_int(resources_int_shared) != 0) {
1404         return -1;
1405     }
1406 #if 0
1407     /* initialize string resources */
1408     if (resources_register_string(resources_string) < 0) {
1409         return -1;
1410     }
1411 #endif
1412     /* initialize int/bool resources */
1413     if (resources_register_int(resources_int_primary_window) < 0) {
1414         return -1;
1415     }
1416 
1417     if (machine_class == VICE_MACHINE_C128) {
1418         if (resources_register_int(resources_int_secondary_window) < 0) {
1419             return -1;
1420         }
1421     }
1422 
1423     for (i = 0; i < NUM_WINDOWS; ++i) {
1424         ui_resources.canvas[i] = NULL;
1425         ui_resources.window_widget[i] = NULL;
1426     }
1427 
1428     return 0;
1429 }
1430 
1431 
1432 /** \brief  Clean up memory used by VICE resources
1433  */
ui_resources_shutdown(void)1434 void ui_resources_shutdown(void)
1435 {
1436 }
1437 
1438 /** \brief Clean up memory used by the UI system itself
1439  */
ui_shutdown(void)1440 void ui_shutdown(void)
1441 {
1442     uidata_shutdown();
1443     ui_statusbar_shutdown();
1444 }
1445 
1446 /** \brief  Update all menu item checkmarks on all windows
1447  *
1448  * \note    This is called from multiple functions in autostart.c and also
1449  *          mon_resource_set() in monitor/monitor.c when they change the
1450  *          value of resources.
1451  *
1452  * \todo    This is unimplemented, but will be much easier to implement if we
1453  *          switch to using a GtkApplication/GMenu based UI.
1454  */
ui_update_menus(void)1455 void ui_update_menus(void)
1456 {
1457     /* NOP: Gtk3 doesn't need this */
1458 }
1459 
1460 
1461 /** \brief  Dispatch next GLib main context event
1462  *
1463  * \warning According to the Gtk3/GLib devs, this will at some point
1464  *          bite us in the arse.
1465  */
ui_dispatch_next_event(void)1466 void ui_dispatch_next_event(void)
1467 {
1468     g_main_context_iteration(g_main_context_default(), FALSE);
1469 }
1470 
1471 
1472 /** \brief  Dispatch events pending in the GLib main context loop
1473  *
1474  * \warning According to the Gtk3/GLib devs, this will at some point
1475  *          bite us in the arse.
1476  */
ui_dispatch_events(void)1477 void ui_dispatch_events(void)
1478 {
1479     while (g_main_context_pending(g_main_context_default())) {
1480         ui_dispatch_next_event();
1481     }
1482 }
1483 
1484 /** \brief  Display the "Do you want to extend the disk image to
1485  *          40-track format?" dialog
1486  *
1487  * \return  nonzero to extend the image, 0 otherwise
1488  *
1489  * \warning This function is not implemented and it will intentionally
1490  *          crash VICE if called.
1491  */
ui_extend_image_dialog(void)1492 int ui_extend_image_dialog(void)
1493 {
1494     /* FIXME: this dialog needs to be implemented. */
1495     NOT_IMPLEMENTED();
1496     return 0;
1497 }
1498 
1499 
1500 /** \brief  Display error message through the UI
1501  *
1502  * \param[in]   format  format string for the error
1503  */
ui_error(const char * format,...)1504 void ui_error(const char *format, ...)
1505 {
1506     char *buffer;
1507     va_list ap;
1508 
1509     va_start(ap, format);
1510     buffer = lib_mvsprintf(format, ap);
1511     va_end(ap);
1512 
1513     vice_gtk3_message_error("VICE Error", buffer);
1514     lib_free(buffer);
1515 }
1516 
1517 
1518 /** \brief  Display a message through the UI
1519  *
1520  * \param[in]   format  format string for message
1521  */
ui_message(const char * format,...)1522 void ui_message(const char *format, ...)
1523 {
1524     char *buffer;
1525     va_list ap;
1526 
1527     va_start(ap, format);
1528     buffer = lib_mvsprintf(format, ap);
1529     va_end(ap);
1530 
1531     vice_gtk3_message_info("VICE Message", buffer);
1532     lib_free(buffer);
1533 }
1534 
1535 #if 0
1536 /** \brief  Display FPS (and some other stuff) in the title bar of each
1537  *          window
1538  *
1539  * \param[in]   percent    CPU speed ratio
1540  * \param[in]   framerate  frame rate
1541  * \param[in]   warp_flag  nonzero if warp mode is active
1542  */
1543 void ui_display_speed(float percent, float framerate, int warp_flag)
1544 {
1545     int i;
1546     char str[128];
1547     int percent_int = (int)(percent + 0.5);
1548     int framerate_int = (int)(framerate + 0.5);
1549     char *warp, *mode[3] = {"", " (VDC)", " (Monitor)"};
1550 
1551     for (i = 0; i < NUM_WINDOWS; i++) {
1552         if (ui_resources.canvas[i] && GTK_WINDOW(ui_resources.window_widget[i])) {
1553             warp = (warp_flag ? "(warp)" : "");
1554             str[0] = 0;
1555             if (machine_class != VICE_MACHINE_VSID) {
1556                 snprintf(str, 128, "%s%s - %3d%%, %2d fps %s%s",
1557                          ui_resources.canvas[i]->viewport->title, mode[i],
1558                          percent_int, framerate_int, warp,
1559                          is_paused ? " (Paused)" : "");
1560             } else {
1561                 snprintf(str, 128, "VSID - %3d%% %s%s",
1562                          percent_int, warp,
1563                          is_paused ? " (Paused)" : "");
1564             }
1565             str[127] = 0;
1566             gtk_window_set_title(GTK_WINDOW(ui_resources.window_widget[i]), str);
1567         }
1568     }
1569 }
1570 #endif
1571 
1572 
1573 /** \brief  Keeps the ui events going while the emulation is paused
1574  *
1575  * \param[in]   addr    unused
1576  * \param[in]   data    unused
1577  */
pause_trap(uint16_t addr,void * data)1578 static void pause_trap(uint16_t addr, void *data)
1579 {
1580     ui_display_paused(1);
1581     vsync_suspend_speed_eval();
1582     while (is_paused) {
1583         ui_dispatch_next_event();
1584         g_usleep(10000);
1585     }
1586 }
1587 
1588 
1589 /** \brief  This should display some 'pause' status indicator on the statusbar
1590  *
1591  * \param[in]   flag    pause state
1592  */
ui_display_paused(int flag)1593 void ui_display_paused(int flag)
1594 {
1595     ui_display_speed(0.0, 0.0, 0);
1596 }
1597 
1598 
1599 /** \brief  Pause emulation
1600  *
1601  * \param[in]   flag    toggle pause state if true
1602  */
ui_pause_emulation(int flag)1603 void ui_pause_emulation(int flag)
1604 {
1605     if (flag && !is_paused) {
1606         is_paused = 1;
1607         interrupt_maincpu_trigger_trap(pause_trap, 0);
1608     } else {
1609         ui_display_paused(0);
1610         is_paused = 0;
1611     }
1612 }
1613 
1614 
1615 
1616 /** \brief  Check if emulation is paused
1617  *
1618  * \return  nonzero if emulation is paused
1619  */
ui_emulation_is_paused(void)1620 int ui_emulation_is_paused(void)
1621 {
1622     return is_paused;
1623 }
1624 
1625 
1626 /** \brief  Pause toggle handler
1627  *
1628  * \return  TRUE (indicates the Alt+P got consumed by Gtk, so it won't be
1629  *          passed to the emu)
1630  *
1631  * \todo    Update UI tickmarks properly if triggered by a keyboard
1632  *          accelerator, or the settings dialog.
1633  */
ui_toggle_pause(void)1634 gboolean ui_toggle_pause(void)
1635 {
1636     ui_pause_emulation(!is_paused);
1637     /* TODO: somehow update the checkmark in the menu without reverting to
1638      *       weird code like Gtk
1639      */
1640     return TRUE;    /* has to be TRUE to avoid passing Alt+P into the emu */
1641 }
1642 
1643 
1644 /** \brief  Toggle warp mode
1645  */
ui_toggle_warp(void)1646 static void ui_toggle_warp(void)
1647 {
1648     ui_toggle_resource(NULL, (gpointer)"WarpMode");
1649 }
1650 
1651 
1652 
1653 /** \brief  Advance frame handler
1654  *
1655  * \return  TRUE (indicates the Alt+SHIFT+P got consumed by Gtk, so it won't be
1656  *          passed to the emu)
1657  *
1658  * \note    The gboolean return value is no longer required since the 'hotkey'
1659  *          handling in kbd.c takes care of passing TRUE to Gtk3.
1660  */
ui_advance_frame(void)1661 gboolean ui_advance_frame(void)
1662 {
1663     if (ui_emulation_is_paused()) {
1664         vsyncarch_advance_frame();
1665     } else {
1666         ui_pause_emulation(1);
1667     }
1668 
1669     return TRUE;    /* has to be TRUE to avoid passing Alt+SHIFT+P into the emu */
1670 }
1671 
1672 /** \brief  Shutdown the UI, clean up resources
1673  */
ui_exit(void)1674 void ui_exit(void)
1675 {
1676     int soe;    /* save on exit */
1677 
1678     /* clean up UI resources */
1679     if (machine_class != VICE_MACHINE_VSID) {
1680         uicart_shutdown();
1681         ui_disk_attach_shutdown();
1682         ui_tape_attach_shutdown();
1683         ui_smart_attach_shutdown();
1684     }
1685 
1686     ui_settings_shutdown();
1687 
1688     /* Destroy the main window(s) */
1689     ui_destroy_main_window(PRIMARY_WINDOW);
1690     ui_destroy_main_window(SECONDARY_WINDOW);
1691 
1692     resources_get_int("SaveResourcesOnExit", &soe);
1693     if (soe) {
1694         resources_save(NULL);
1695     }
1696 
1697     /* unregister the CBM font */
1698     archdep_unregister_cbmfont();
1699 
1700     /* deallocate memory used by the unconnected keyboard shortcuts */
1701     kbd_hotkey_shutdown();
1702 
1703     /* trigger any remaining Gtk/GLib events */
1704     while (g_main_context_pending(g_main_context_default())) {
1705         debug_gtk3("processing pending event.");
1706         g_main_context_iteration(g_main_context_default(), TRUE);
1707     }
1708     archdep_vice_exit(0);
1709 }
1710 
1711 /** \brief  Send current light pen state to the emulator core for all windows
1712  */
ui_update_lightpen(void)1713 void ui_update_lightpen(void)
1714 {
1715     video_canvas_t *canvas;
1716     canvas = ui_resources.canvas[PRIMARY_WINDOW];
1717     if (machine_class == VICE_MACHINE_C128) {
1718         /* According to lightpen.c, x128 flips primary and secondary
1719          * windows compared to what the GTK3 backend expects. */
1720         if (canvas) {
1721             lightpen_update(1, canvas->pen_x, canvas->pen_y, canvas->pen_buttons);
1722         }
1723         canvas = ui_resources.canvas[SECONDARY_WINDOW];
1724     }
1725     if (canvas) {
1726         lightpen_update(0, canvas->pen_x, canvas->pen_y, canvas->pen_buttons);
1727     }
1728 }
1729 
1730 
1731 /** \brief  Enable/disable CRT controls
1732  *
1733  * \param[in]   enabled enabled state for the CRT controls
1734  */
ui_enable_crt_controls(int enabled)1735 void ui_enable_crt_controls(int enabled)
1736 {
1737     GtkWidget *window;
1738     GtkWidget *grid;
1739     GtkWidget *crt;
1740 
1741     if (active_win_index < 0 || active_win_index >= NUM_WINDOWS) {
1742         /* No window created yet, most likely. */
1743         return;
1744     }
1745 
1746     window = ui_resources.window_widget[active_win_index];
1747     grid = gtk_bin_get_child(GTK_BIN(window));
1748     crt = gtk_grid_get_child_at(GTK_GRID(grid), 0, ROW_CRT_CONTROLS);
1749 
1750     if (enabled) {
1751         gtk_widget_show(crt);
1752     } else {
1753         gtk_widget_hide(crt);
1754         /*
1755          * This is completely counter-intuitive, but it works, unlike all other
1756          * size_request()/set_size_hint() stuff.
1757          * Appearently setting a size of 1x1 pixels forces Gtk3 to render the
1758          * window to the appropriate (minimum) size,
1759          */
1760         gtk_window_resize(GTK_WINDOW(window), 1, 1);
1761     }
1762 }
1763 
1764 
1765 /** \brief  Enable/disable mixer controls
1766  *
1767  * \param[in]   enabled enabled state for the mixer controls
1768  */
ui_enable_mixer_controls(int enabled)1769 void ui_enable_mixer_controls(int enabled)
1770 {
1771     GtkWidget *window;
1772     GtkWidget *grid;
1773     GtkWidget *mixer;
1774 
1775     if (active_win_index < 0 || active_win_index >= NUM_WINDOWS) {
1776         /* No window created yet, most likely. */
1777         return;
1778     }
1779 
1780     window = ui_resources.window_widget[active_win_index];
1781     grid = gtk_bin_get_child(GTK_BIN(window));
1782     mixer = gtk_grid_get_child_at(GTK_GRID(grid), 0, ROW_MIXER_CONTROLS);
1783 
1784     if (enabled) {
1785         gtk_widget_show(mixer);
1786     } else {
1787         gtk_widget_hide(mixer);
1788         /*
1789          * This is completely counter-intuitive, but it works, unlike all other
1790          * size_request()/set_size_hint() stuff.
1791          * Appearently setting a size of 1x1 pixels forces Gtk3 to render the
1792          * window to the appropriate (minimum) size,
1793          */
1794         gtk_window_resize(GTK_WINDOW(window), 1, 1);
1795     }
1796 }
1797 
1798 
1799 
ui_get_window_by_index(int index)1800 GtkWidget *ui_get_window_by_index(int index)
1801 {
1802     if (index < 0 || index >= NUM_WINDOWS) {
1803         debug_gtk3("invalid window index %d.", index);
1804         return NULL;
1805     }
1806     return ui_resources.window_widget[index];
1807 }
1808