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