1 /** \file   uistatusbar.c
2  *  \brief  Native GTK3 UI statusbar stuff
3  *
4  *  The status bar widget is part of every machine window. This widget
5  *  reacts to UI notifications from the emulation core and otherwise
6  *  does not interact with the rest of the main menu.
7  *
8  *  Functions described as "Statusbar API functions" are called by the
9  *  rest of the UI or the emulation system itself to report that the
10  *  status displays must be updated to reflect possibly new
11  *  information. It is not necessary for the data to be truly new; the
12  *  statusbar implementation will treat repeated reports of the same
13  *  state as no-ops when necessary for performance.
14  *
15  *  \author Marco van den Heuvel <blackystardust68@yahoo.com>
16  *  \author Michael C. Martin <mcmartin@gmail.com>
17  */
18 
19 /*
20  * This file is part of VICE, the Versatile Commodore Emulator.
21  * See README for copyright notice.
22  *
23  *  This program is free software; you can redistribute it and/or modify
24  *  it under the terms of the GNU General Public License as published by
25  *  the Free Software Foundation; either version 2 of the License, or
26  *  (at your option) any later version.
27  *
28  *  This program is distributed in the hope that it will be useful,
29  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
30  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31  *  GNU General Public License for more details.
32  *
33  *  You should have received a copy of the GNU General Public License
34  *  along with this program; if not, write to the Free Software
35  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
36  *  02111-1307  USA.
37  *
38  */
39 
40 #include "vice.h"
41 
42 #include <stdio.h>
43 #include <gtk/gtk.h>
44 
45 #include "debug_gtk3.h"
46 
47 #include "vice_gtk3.h"
48 #include "datasette.h"
49 #include "drive.h"
50 #include "joyport.h"
51 #include "lib.h"
52 #include "machine.h"
53 #include "resources.h"
54 #include "types.h"
55 #include "ui.h"
56 #include "uiapi.h"
57 #include "uidatasette.h"
58 #include "uidiskattach.h"
59 #include "uifliplist.h"
60 #include "uisettings.h"
61 #include "userport/userport_joystick.h"
62 
63 #include "diskcontents.h"
64 #include "drive.h"
65 #include "drivetypes.h"
66 #include "diskimage.h"
67 #include "diskimage/fsimage.h"
68 #include "autostart.h"
69 #include "tapecontents.h"
70 
71 #include "contentpreviewwidget.h"
72 #include "dirmenupopup.h"
73 #include "joystickmenupopup.h"
74 #include "statusbarspeedwidget.h"
75 
76 #include "uistatusbar.h"
77 
78 
79 /** \brief The maximum number of status bars we will permit to exist
80  *         at once. */
81 #define MAX_STATUS_BARS 3
82 
83 
84 /** \brief  Status bar column indici
85  *
86  * These values assume a proper emulator statusbar (ie not VSID).
87  */
88 enum {
89     SB_COL_SPEED = 0,   /**< message widget */
90     SB_COL_SEP_MSG ,    /**< separator between message and ctr/mixer widgets */
91     SB_COL_CRT,         /**< crt and mixer widgets */
92     SB_COL_SEP_CRT,     /**< separator between crt/mixer and tape widgets */
93     SB_COL_TAPE,        /**< tape and joysticks widget */
94     SB_COL_SEP_TAPE,    /**< separator between tape and joysticks widgets */
95     SB_COL_DRIVE        /**< drives widgets */
96 };
97 
98 
99 /** \brief Global data that custom status bar widgets base their rendering
100  *         on.
101  *
102  *  This data is usually set by calls from the emulation core or from
103  *  other parts of the UI in response to user commands or I/O events.
104  *
105  *  \todo The PET can have two tape drives.
106  *
107  *  \todo Two-unit drive units are not covered by this structure.
108  */
109 typedef struct ui_sb_state_s {
110     /** \brief Identifier for the currently displayed status bar
111      * message.
112      *
113      * Used to correlate timeout events so that a new message
114      * isn't erased by some older message timing out. */
115     intptr_t statustext_msgid;
116     /** \brief Current tape state (play, rewind, etc) */
117     int tape_control;
118     /** \brief Nonzero if the tape motor is powered. */
119     int tape_motor_status;
120     /** \brief Location on the tape */
121     int tape_counter;
122     /** \brief Which drives are to be displayed in the status bar.
123      *
124      *  This is a bitmask, with bits 0-3 representing drives 8-11,
125      *  respectively.
126      */
127     int drives_enabled;
128     /** \brief Nonzero if True Drive Emulation is active and drive
129      *         LEDs should be drawn. */
130     int drives_tde_enabled;
131     /** \brief Color descriptors for the drive LED colors.
132      *
133      *  This value is a bitmask, with bit 0 and 1 set if the
134      *  corresponding LED is green. Otherwise it is red. Drives that
135      *  only have one LED will have their 'second' LED permanently at
136      *  intensity zero so the value is irrelevant in that case. */
137     int drive_led_types[DRIVE_NUM];
138     /** \brief Current intensity of each drive LED, 0=off,
139      *         1000=max. */
140     unsigned int current_drive_leds[DRIVE_NUM][2];
141     /** \brief Current state for each of the joyports.
142      *
143      *  This is an 7-bit bitmask, representing, from least to most
144      *  significant bits: up, down, left, right, fire button,
145      *  secondary fire button, tertiary fire button. */
146     int current_joyports[JOYPORT_MAX_PORTS];
147     /** \brief Which joystick ports are actually available.
148      *
149      *  This is a bitmask representing notional ports 0-4, which are
150      *  themselves defined in joyport/joyport.h. Cases like a SIDcart
151      *  control port on a Plus/4 without other userport control ports
152      *  mean that the set of active joyports may be discontinuous. */
153     int joyports_enabled;
154 } ui_sb_state_t;
155 
156 /** \brief The current state of the status bars across the UI. */
157 static ui_sb_state_t sb_state;
158 
159 /** \brief The full structure representing a status bar widget.
160  *
161  *  This includes the top-level widget and then every subwidget that
162  *  needs to be individually addressed or manipulated by the
163  *  status-report API. */
164 typedef struct ui_statusbar_s {
165     /** \brief The status bar widget proper.
166      *
167      *  This is the widget the rest of the UI code will store and pack
168      *  into windows. */
169     GtkWidget *bar;
170 #if 0
171     /** \brief The status message widget. */
172     GtkLabel *msg;
173 #endif
174     /** \brief  Widget displaying CPU speed and FPS
175      *
176      * Also used to set refresh rate, CPU speed, pause, warp and adv-frame
177      */
178     GtkWidget *speed;
179 
180     /** \brief CRT control widget checkbox */
181     GtkWidget *crt;
182 
183     /** \brief  Mixer control widget checkbox */
184     GtkWidget *mixer;
185 
186     /** \brief The Tape Status widget. */
187     GtkWidget *tape;
188     /** \brief The Tape Status widget's popup menu. */
189     GtkWidget *tape_menu;
190     /** \brief The joyport status widget. */
191     GtkWidget *joysticks;
192     /** \brief The drive status widgets. */
193     GtkWidget *drives[DRIVE_NUM];
194     /** \brief The popup menus associated with each drive. */
195     GtkWidget *drive_popups[DRIVE_NUM];
196     /** \brief The hand-shaped cursor to change to when popup menus
197      *         are available. */
198     GdkCursor *hand_ptr;
199 } ui_statusbar_t;
200 
201 /** \brief The collection of status bars currently active.
202  *
203  *  Inactive status bars have a NULL pointer for their "bar" field. */
204 static ui_statusbar_t allocated_bars[MAX_STATUS_BARS];
205 
206 
207 /* Forward decl. */
208 static void tape_dir_autostart_callback(const char *image, int index);
209 
210 
211 /** \brief Initialize the status bar subsystem.
212  *
213  *  \warning This function _must_ be called before any call to
214  *           ui_statusbar_create() and _must not_ be called after any
215  *           call to it.
216  */
ui_statusbar_init(void)217 void ui_statusbar_init(void)
218 {
219     int i, j;
220 
221     for (i = 0; i < MAX_STATUS_BARS; ++i) {
222         allocated_bars[i].bar = NULL;
223         allocated_bars[i].speed = NULL;
224         allocated_bars[i].crt = NULL;
225         allocated_bars[i].mixer = NULL;
226         allocated_bars[i].tape = NULL;
227         allocated_bars[i].tape_menu = NULL;
228         allocated_bars[i].joysticks = NULL;
229         for (j = 0; j < DRIVE_NUM; ++j) {
230             allocated_bars[i].drives[j] = NULL;
231             allocated_bars[i].drive_popups[j] = NULL;
232         }
233         allocated_bars[i].hand_ptr = NULL;
234     }
235 
236     sb_state.statustext_msgid = 0;
237     sb_state.tape_control = 0;
238     sb_state.tape_motor_status = 0;
239     sb_state.tape_counter = 0;
240     sb_state.drives_enabled = 0;
241     sb_state.drives_tde_enabled = 0;
242     for (i = 0; i < DRIVE_NUM; ++i) {
243         sb_state.drive_led_types[i] = 0;
244         sb_state.current_drive_leds[i][0] = 0;
245         sb_state.current_drive_leds[i][1] = 0;
246     }
247 
248     for (i = 0; i < JOYPORT_MAX_PORTS; ++i) {
249         sb_state.current_joyports[i] = 0;
250     }
251     sb_state.joyports_enabled = 0;
252 }
253 
254 /** \brief Clean up any resources the statusbar system uses that
255  *         weren't cleaned up when the status bars themselves were
256  *         destroyed. */
ui_statusbar_shutdown(void)257 void ui_statusbar_shutdown(void)
258 {
259     /* There are no such resources, so this is a no-op */
260 }
261 
262 /** \brief Extracts the list of enabled drives from the DriveType
263  *         resources.
264  *
265  *  \return A bitmask value suitable for ui_sb_state_s::drives_enabled.
266  */
compute_drives_enabled_mask(void)267 static int compute_drives_enabled_mask(void)
268 {
269     int unit, mask;
270     int result = 0;
271     for (unit = 0, mask = 1; unit < 4; ++unit, mask <<= 1) {
272         int status = 0, value = 0;
273         status = resources_get_int_sprintf("Drive%dType", &value, unit + 8);
274         if (status == 0 && value != 0) {
275             result |= mask;
276         }
277     }
278     return result;
279 }
280 
281 /** \brief Draws the tape icon based on the current control and motor status.
282  *
283  *  \param widget  The tape icon GtkDrawingArea being drawn to.
284  *  \param cr      The cairo context that handles the drawing.
285  *  \param data    Ignored, but mandated by the function signature
286  *
287  *  \return FALSE, telling GTK to continue event processing
288  *
289  *  \todo Once multiple tape drives are supported, the data parameter
290  *        will be the integer index of which tape drive this widget
291  *        represents.
292  */
draw_tape_icon_cb(GtkWidget * widget,cairo_t * cr,gpointer data)293 static gboolean draw_tape_icon_cb(GtkWidget *widget, cairo_t *cr, gpointer data)
294 {
295     int width, height;
296     double x, y, inset;
297     width = gtk_widget_get_allocated_width(widget);
298     height = gtk_widget_get_allocated_height(widget);
299     if (width > height) {
300         x = (width - height) / 2.0;
301         y = 0.0;
302         inset = height / 10.0;
303     } else {
304         x = 0.0;
305         y = (height - width) / 2.0;
306         inset = width / 10.0;
307     }
308 
309     if (sb_state.tape_motor_status) {
310         cairo_set_source_rgb(cr, 0, 0.75, 0);
311     } else {
312         cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
313     }
314     cairo_rectangle(cr, x + inset, y + inset, inset * 8, inset * 8);
315     cairo_fill(cr);
316 
317     cairo_set_source_rgb(cr, 0, 0, 0);
318     switch (sb_state.tape_control) {
319     case DATASETTE_CONTROL_STOP:
320         cairo_rectangle(cr, x + 2.5*inset, y + 2.5*inset, inset * 5, inset * 5);
321         cairo_fill(cr);
322         break;
323     case DATASETTE_CONTROL_START:
324         cairo_move_to(cr, x + 3*inset, y + 2.5*inset);
325         cairo_line_to(cr, x + 3*inset, y + 7.5*inset);
326         cairo_line_to(cr, x + 7*inset, y + 5*inset);
327         cairo_close_path(cr);
328         cairo_fill(cr);
329         break;
330     case DATASETTE_CONTROL_FORWARD:
331         cairo_move_to(cr, x + 2.5*inset, y + 2.5*inset);
332         cairo_line_to(cr, x + 2.5*inset, y + 7.5*inset);
333         cairo_line_to(cr, x + 5*inset, y + 5*inset);
334         cairo_close_path(cr);
335         cairo_fill(cr);
336         cairo_move_to(cr, x + 5*inset, y + 2.5*inset);
337         cairo_line_to(cr, x + 5*inset, y + 7.5*inset);
338         cairo_line_to(cr, x + 7.5*inset, y + 5*inset);
339         cairo_close_path(cr);
340         cairo_fill(cr);
341         break;
342     case DATASETTE_CONTROL_REWIND:
343         cairo_move_to(cr, x + 5*inset, y + 2.5*inset);
344         cairo_line_to(cr, x + 5*inset, y + 7.5*inset);
345         cairo_line_to(cr, x + 2.5*inset, y + 5*inset);
346         cairo_close_path(cr);
347         cairo_fill(cr);
348         cairo_move_to(cr, x + 7.5*inset, y + 2.5*inset);
349         cairo_line_to(cr, x + 7.5*inset, y + 7.5*inset);
350         cairo_line_to(cr, x + 5*inset, y + 5*inset);
351         cairo_close_path(cr);
352         cairo_fill(cr);
353         break;
354     case DATASETTE_CONTROL_RECORD:
355         cairo_new_sub_path(cr);
356         cairo_arc(cr, x + 5*inset, y + 5*inset, 2.5*inset, 0, 2 * G_PI);
357         cairo_close_path(cr);
358         cairo_fill(cr);
359         cairo_set_source_rgb(cr, 1, 0, 0);
360         cairo_new_sub_path(cr);
361         cairo_arc(cr, x + 5*inset, y + 5*inset, 2*inset, 0, 2 * G_PI);
362         cairo_close_path(cr);
363         cairo_fill(cr);
364         break;
365     case DATASETTE_CONTROL_RESET:
366     case DATASETTE_CONTROL_RESET_COUNTER:
367     default:
368         /* Things that aren't really controls look like we stop it. */
369         /* TODO: Should RESET_COUNTER be wiped out by the time it gets here? */
370         cairo_rectangle(cr, x + 2.5*inset, y + 2.5*inset, inset * 5, inset * 5);
371         cairo_fill(cr);
372     }
373 
374     return FALSE;
375 }
376 
377 
378 /** \brief  Handler for the 'activate' event of the 'Configure drives' item
379  *
380  * Opens the settings UI at the drive settings "page".
381  *
382  * \param[in]   widget  menu item triggering the event (unused)
383  * \param[in]   data    extra event data (unused)
384  */
on_drive_configure_activate(GtkWidget * widget,gpointer data)385 static void on_drive_configure_activate(GtkWidget *widget, gpointer data)
386 {
387     ui_settings_dialog_create_and_activate_node("peripheral/drive");
388 }
389 
390 
391 /** \brief  Handler for the 'activate' event of the 'Reset drive #X' item
392  *
393  * Triggers a reset for drive ((int)data + 8)
394  *
395  * \param[in]   widget  menu item triggering the event (unused)
396  * \param[in]   data    drive number (0-3)
397  */
on_drive_reset_clicked(GtkWidget * widget,gpointer data)398 static void on_drive_reset_clicked(GtkWidget *widget, gpointer data)
399 {
400     debug_gtk3("Resetting drive %d", GPOINTER_TO_INT(data) + 8);
401     drive_cpu_trigger_reset(GPOINTER_TO_INT(data));
402 }
403 
404 
405 /** \brief Draw the LED associated with some drive's LED state.
406  *
407  *  \param widget  The drive LED GtkDrawingArea being drawn to.
408  *  \param cr      The cairo context that handles the drawing.
409  *  \param data    The index (0-3) of which drive this represents.
410  *
411  *  \return FALSE, telling GTK to continue event processing
412  */
draw_drive_led_cb(GtkWidget * widget,cairo_t * cr,gpointer data)413 static gboolean draw_drive_led_cb(GtkWidget *widget, cairo_t *cr, gpointer data)
414 {
415     int width, height, drive, i;
416     double red = 0.0, green = 0.0, x, y, w, h;
417 
418     width = gtk_widget_get_allocated_width(widget);
419     height = gtk_widget_get_allocated_height(widget);
420     drive = GPOINTER_TO_INT(data);
421     for (i = 0; i < 2; ++i) {
422         int led_color = sb_state.drive_led_types[drive] & (1 << i);
423         if (led_color) {
424             green += sb_state.current_drive_leds[drive][i] / 1000.0;
425         } else {
426             red += sb_state.current_drive_leds[drive][i] / 1000.0;
427         }
428     }
429     /* Cairo clamps these for us */
430     cairo_set_source_rgb(cr, red, green, 0);
431     /* LED is half text height and aims for a 2x1 aspect ratio */
432     h = height / 2.0;
433     w = 2.0 * h;
434     x = (width / 2.0) - h;
435     y = height / 4.0;
436     cairo_rectangle(cr, x, y, w, h);
437     cairo_fill(cr);
438     return FALSE;
439 }
440 
441 /** \brief Draw the current input status from a joyport.
442  *
443  *  This produces five squares arranged in a + shape, with directions
444  *  represented as green squares when active and black when not. The
445  *  fire buttons are represented by the central square, with red,
446  *  green, and blue components representing the three possible
447  *  buttons.
448  *
449  *  For traditional Commodore joysticks, there is only one fire button
450  *  and this will be diplayed as a red square when the button is
451  *  pressed.
452  *
453  *  \param widget  The joyport GtkDrawingArea being drawn to.
454  *  \param cr      The cairo context that handles the drawing.
455  *  \param data    The index (0-4) of which joyport this represents.
456  *
457  *  \return FALSE, telling GTK to continue event processing
458  */
draw_joyport_cb(GtkWidget * widget,cairo_t * cr,gpointer data)459 static gboolean draw_joyport_cb(GtkWidget *widget, cairo_t *cr, gpointer data)
460 {
461     int width, height, val;
462     double e, s, x, y;
463 
464     width = gtk_widget_get_allocated_width(widget);
465     height = gtk_widget_get_allocated_height(widget);
466     val = sb_state.current_joyports[GPOINTER_TO_INT(data)];
467 
468     /* This widget "wants" to draw 6x6 squares inside a 20x20
469      * space. We compute x and y offsets for a scaled square within
470      * the real widget space, and then the actual widths for a square
471      * edge (e) and the spaces between them (s). */
472 
473     if (width > height) {
474         s = height / 20.0;
475         x = (width - height) / 2.0;
476         y = 0.0;
477     } else {
478         s = width / 20.0;
479         y = (height - width) / 2.0;
480         x = 0.0;
481     }
482     e = s * 5.0;
483 
484     /* Then we render the five squares. This seems like it could be
485      * done more programatically, but enough changes each iteration
486      * that we might as well unroll it. */
487 
488     /* Up: Bit 0 */
489     cairo_set_source_rgb(cr, 0, (val&0x01) ? 1 : 0, 0);
490     cairo_rectangle(cr, x + e + 2*s, y+s, e, e);
491     cairo_fill(cr);
492     /* Down: Bit 1 */
493     cairo_set_source_rgb(cr, 0, (val&0x02) ? 1 : 0, 0);
494     cairo_rectangle(cr, x + e + 2*s, y + 2*e + 3*s, e, e);
495     cairo_fill(cr);
496     /* Left: Bit 2 */
497     cairo_set_source_rgb(cr, 0, (val&0x04) ? 1 : 0, 0);
498     cairo_rectangle(cr, x + s, y + e + 2*s, e, e);
499     cairo_fill(cr);
500     /* Right: Bit 3 */
501     cairo_set_source_rgb(cr, 0, (val&0x08) ? 1 : 0, 0);
502     cairo_rectangle(cr, x + 2*e + 3*s, y + e + 2*s, e, e);
503     cairo_fill(cr);
504     /* Fire buttons: Bits 4-6. Each of the three notional fire buttons
505      * controls the red, green, or blue color of the fire button
506      * area. By default, we are using one-button joysticks and so this
507      * region will be either black or red. */
508     cairo_set_source_rgb(cr, (val&0x10) ? 1 : 0,
509                              (val&0x20) ? 1 : 0,
510                              (val&0x40) ? 1 : 0);
511     cairo_rectangle(cr, x + e + 2*s, y + e + 2*s, e, e);
512     cairo_fill(cr);
513     return FALSE;
514 }
515 
516 /** \brief Create a new drive widget for inclusion in the status bar.
517  *
518  *  \param unit The drive unit to create (0-3, indicating devices
519  *              8-11)
520  *
521  *  \return The constructed widget. This widget will be a floating
522  *          reference.
523  */
ui_drive_widget_create(int unit)524 static GtkWidget *ui_drive_widget_create(int unit)
525 {
526     GtkWidget *grid, *number, *track, *led;
527     char drive_id[4];
528 
529     grid = gtk_grid_new();
530     gtk_orientable_set_orientation(GTK_ORIENTABLE(grid), GTK_ORIENTATION_HORIZONTAL);
531     gtk_widget_set_hexpand(grid, FALSE);
532 
533     snprintf(drive_id, 4, "%d:", unit + 8);
534     drive_id[3] = 0;
535     number = gtk_label_new(drive_id);
536     gtk_widget_set_halign(number, GTK_ALIGN_START);
537 
538     track = gtk_label_new("18.5");
539     gtk_widget_set_hexpand(track, TRUE);
540     gtk_widget_set_halign(track, GTK_ALIGN_END);
541 
542     led = gtk_drawing_area_new();
543     gtk_widget_set_size_request(led, 30, 15);
544     gtk_widget_set_no_show_all(led, TRUE);
545     gtk_container_add(GTK_CONTAINER(grid), number);
546     gtk_container_add(GTK_CONTAINER(grid), track);
547     gtk_container_add(GTK_CONTAINER(grid), led);
548     /* Labels will notice clicks by default, but drawing areas need to
549      * be told to. */
550     gtk_widget_add_events(led, GDK_BUTTON_PRESS_MASK);
551     g_signal_connect(led, "draw", G_CALLBACK(draw_drive_led_cb), GINT_TO_POINTER(unit));
552     return grid;
553 }
554 
555 
556 /** \brief Respond to mouse clicks on the tape status widget.
557  *
558  *  This displays the tape control popup menu.
559  *
560  *  \param widget  The GtkWidget that received the click. Ignored.
561  *  \param event   The event representing the bottom operation.
562  *  \param data    An integer representing which window's status bar was
563  *                 clicked and thus where the popup window should go.
564  *
565  *  \return TRUE if further event processing should be skipped.
566  *
567  *  \todo This callback and the way it is configured both will need to
568  *        be significantly reworked to manage multiple tape drives.
569  */
ui_do_datasette_popup(GtkWidget * widget,GdkEvent * event,gpointer data)570 static gboolean ui_do_datasette_popup(GtkWidget *widget, GdkEvent *event, gpointer data)
571 {
572     int i = GPOINTER_TO_INT(data);
573 
574     if (((GdkEventButton *)event)->button == GDK_BUTTON_PRIMARY) {
575         if (allocated_bars[i].tape && allocated_bars[i].tape_menu
576                 && event->type == GDK_BUTTON_PRESS) {
577             gtk_menu_popup_at_widget(GTK_MENU(allocated_bars[i].tape_menu),
578                                      allocated_bars[i].tape,
579                                      GDK_GRAVITY_NORTH_EAST,
580                                      GDK_GRAVITY_SOUTH_EAST,
581                                      event);
582         }
583         return TRUE;
584     } else if (((GdkEventButton *)event)->button == GDK_BUTTON_SECONDARY) {
585         GtkWidget *dir_menu;
586         debug_gtk3("Got SECONDARY BUTTON");
587 
588         dir_menu = dir_menu_popup_create(-1, tapecontents_read,
589                 tape_dir_autostart_callback);
590         gtk_menu_popup_at_widget(GTK_MENU(dir_menu),
591                                  widget,
592                                  GDK_GRAVITY_NORTH_EAST,
593                                  GDK_GRAVITY_SOUTH_EAST,
594                                  event);
595         return TRUE;
596     }
597     return FALSE;
598 }
599 
600 #if 0
601 /** \brief  Handler for the "response" event of the directory dialog
602  *
603  * \param[in,out]   dialog      dialog triggering the event
604  * \param[in]       response_id response ID
605  * \param[in]       user_data   extra event data (unused)
606  */
607 static void on_response_dir_widget(GtkDialog *dialog,
608                                    gint response_id,
609                                    gpointer user_data)
610 {
611     debug_gtk3("got response ID = %d.", response_id);
612     switch (response_id) {
613         case GTK_RESPONSE_CLOSE:
614             gtk_widget_destroy(GTK_WIDGET(dialog));
615             break;
616         default:
617             debug_gtk3("unhandled response ID %d,", response_id);
618     }
619 }
620 #endif
621 
622 #if 0
623 /** \brief  Handler for the double-click event of the directory listing
624  *
625  * \param[in,out]   dialog      dialog triggering the event
626  * \param[in]       autostart   autostart flag (FIXME: unused, this is due to
627  *                              a hack in the other widgets that use the content
628  *                              preview widget)
629  * \param[in]       dir_index   directory index of the file to autosart
630  */
631 static void on_dir_widget_selected(GtkWidget *widget,
632                                    gint autostart,
633                                    gpointer dir_index)
634 {
635     int index = GPOINTER_TO_INT(dir_index);
636     debug_gtk3("called with file index %d.", index);
637     autostart_disk(autostart_diskimage, NULL, index,
638             AUTOSTART_MODE_RUN);
639     gtk_widget_destroy(widget);
640 }
641 #endif
642 
643 
disk_dir_autostart_callback(const char * image,int index)644 static void disk_dir_autostart_callback(const char *image, int index)
645 {
646     const char *autostart_image;
647 
648     debug_gtk3("Got image '%s', file index %d to autostart",
649             image, index);
650     /* make a copy of the image name since autostart will reattach the disk
651      * image, freeing memory used by the image name passed to us in the process
652      */
653     autostart_image = lib_stralloc(image);
654     autostart_disk(autostart_image, NULL, index + 1, AUTOSTART_MODE_RUN);
655     lib_free(autostart_image);
656 }
657 
658 
659 
tape_dir_autostart_callback(const char * image,int index)660 static void tape_dir_autostart_callback(const char *image, int index)
661 {
662     const char *autostart_image;
663 
664     debug_gtk3("Got image '%s', file index %d to autostart",
665             image, index);
666     /* make a copy of the image name since autostart will reattach the tape
667      * image, freeing memory used by the image name passed to us in the process
668      */
669     autostart_image = lib_stralloc(image);
670     autostart_tape(autostart_image, NULL, index + 1, AUTOSTART_MODE_RUN);
671     lib_free(autostart_image);
672 }
673 
674 
675 #if 0
676 /** \brief  Show dialog to run a file of the image attached to \a unit
677  *
678  * Double clicking on a file will autorun that file. Clicking on Cancel or the
679  * 'X' in the Window will cancel the operation.
680  *
681  * \param[in]   dev     drive index (0-3)
682  *
683  * \return  GtkDialog
684  */
685 static GtkWidget *ui_statusbar_dir_dialog_create(int dev)
686 {
687     GtkWidget *dialog;
688     GtkWidget *content;
689     GtkWidget *preview;
690     struct drive_context_s *drv_ctx;
691     struct drive_s *drv_s;
692     struct disk_image_s *image;
693 
694     autostart_diskimage = NULL;
695 
696     debug_gtk3("Got drive index #%d", dev);
697     dialog = gtk_dialog_new_with_buttons(
698             "Select file to autostart",
699             ui_get_active_window(),
700             GTK_DIALOG_MODAL,
701             "Cancel", GTK_RESPONSE_CLOSE,
702             NULL);
703 
704     content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
705     preview = content_preview_widget_create(GTK_WIDGET(dialog),
706             diskcontents_filesystem_read,
707             on_dir_widget_selected);
708     gtk_widget_set_size_request(preview, 300, 500);
709 
710     /*
711      * The following is complete horseshit, this needs to be implemented in a
712      * function in drive/vdrive somehow. This much dereferencing in UI code
713      * is not normal method.
714      */
715     drv_ctx = drive_context[dev];
716     if (drv_ctx != NULL) {
717         drv_s = drv_ctx->drive;
718         if (drv_s != NULL) {
719             image = drv_s->image;
720             if (image != NULL) {
721                 /* this assumes fsimage, not real-image */
722                 struct fsimage_s *fsimg = image->media.fsimage;
723                 if (fsimg != NULL) {
724                     autostart_diskimage = fsimg->name;
725                 }
726             }
727         }
728     }
729 
730     debug_gtk3("reading dir of '%s'", autostart_diskimage);
731     if (autostart_diskimage != NULL) {
732         content_preview_widget_set_image(preview, autostart_diskimage);
733     }
734 
735     gtk_container_add(GTK_CONTAINER(content), preview);
736     return dialog;
737 }
738 #endif
739 
740 
741 /** \brief Respond to mouse clicks on a disk drive status widget.
742  *
743  *  This displays the drive control popup menu.
744  *
745  *  \param widget  The GtkWidget that received the click. Ignored.
746  *  \param event   The event representing the bottom operation.
747  *  \param data    An integer representing which window's status bar was
748  *                 clicked and thus where the popup window should go.
749  *
750  *  \return TRUE if further event processing should be skipped.
751  *
752  *  \todo This function uses GTK3 version checking to avoid deprecated
753  *        functions on version 3.22 and to avoid nonexistent functions
754  *        on version 3.16 through 3.20. Once 3.22 becomes a
755  *        requirement, this version checking should be eliminated.
756  */
ui_do_drive_popup(GtkWidget * widget,GdkEvent * event,gpointer data)757 static gboolean ui_do_drive_popup(GtkWidget *widget, GdkEvent *event, gpointer data)
758 {
759     int i = GPOINTER_TO_INT(data);
760     GtkWidget *drive_menu = allocated_bars[0].drive_popups[i];
761     GtkWidget *drive_menu_item;
762     gchar buffer[256];
763 
764     ui_populate_fliplist_menu(drive_menu, i + 8, 0);
765 
766     /* XXX: this code is a duplicate of the drive_menu creation code, so we
767      *      should probably refactor this a bit
768      */
769     gtk_container_add(GTK_CONTAINER(drive_menu),
770             gtk_separator_menu_item_new());
771     drive_menu_item = gtk_menu_item_new_with_label("Configure drives ...");
772     g_signal_connect(drive_menu_item, "activate",
773             G_CALLBACK(on_drive_configure_activate), NULL);
774     gtk_container_add(GTK_CONTAINER(drive_menu), drive_menu_item);
775 
776     /*
777      * Add drive reset item
778      */
779     g_snprintf(buffer, 256, "Reset drive #%d", i + 8);
780     drive_menu_item = gtk_menu_item_new_with_label(buffer);
781     g_signal_connect(drive_menu_item, "activate",
782             G_CALLBACK(on_drive_reset_clicked), GINT_TO_POINTER(i));
783     gtk_container_add(GTK_CONTAINER(drive_menu), drive_menu_item);
784 
785     gtk_widget_show_all(drive_menu);
786     /* 3.22 isn't available on the latest stable version of all
787      * distros yet. This is expected to change when Ubuntu 18.04 is
788      * released. Since popup handling is the only thing 3.22 requires
789      * be done differently than its predecessors, this is the only
790      * place we should rely on version checks. */
791 
792     if (((GdkEventButton *)event)->button == GDK_BUTTON_PRIMARY) {
793         /* show popup for attaching/detaching disk images */
794         gtk_menu_popup_at_widget(GTK_MENU(drive_menu),
795                                  widget,
796                                  GDK_GRAVITY_NORTH_EAST,
797                                  GDK_GRAVITY_SOUTH_EAST,
798                                  event);
799     } else if (((GdkEventButton *)event)->button == GDK_BUTTON_SECONDARY) {
800         /* show popup to run file in currently attached image */
801         GtkWidget *dir_menu = dir_menu_popup_create(
802                 i,
803                 diskcontents_filesystem_read,
804                 disk_dir_autostart_callback);
805 #if 0
806         GtkWidget *dialog;
807 
808         debug_gtk3("Got secondary button click event");
809         dialog = ui_statusbar_dir_dialog_create(i);
810         g_signal_connect(dialog, "response",
811                 G_CALLBACK(on_response_dir_widget), NULL);
812         gtk_widget_show_all(dialog);
813 #endif
814         /* show popup for selecting file in currently attached image */
815         gtk_menu_popup_at_widget(GTK_MENU(dir_menu),
816                                  widget,
817                                  GDK_GRAVITY_NORTH_EAST,
818                                  GDK_GRAVITY_SOUTH_EAST,
819                                  event);
820     }
821 
822     return TRUE;
823 }
824 
825 /** \brief Create a new tape widget for inclusion in the status bar.
826  *
827  *  \return The constructed widget. This widget will be a floating
828  *          reference.
829  */
ui_tape_widget_create(void)830 static GtkWidget *ui_tape_widget_create(void)
831 {
832     GtkWidget *grid, *header, *counter, *state;
833 
834     grid = gtk_grid_new();
835     gtk_orientable_set_orientation(GTK_ORIENTABLE(grid), GTK_ORIENTATION_HORIZONTAL);
836     gtk_widget_set_hexpand(grid, FALSE);
837     header = gtk_label_new("Tape:");
838     gtk_widget_set_hexpand(header, TRUE);
839     gtk_widget_set_halign(header, GTK_ALIGN_START);
840 
841     counter = gtk_label_new("000");
842     state = gtk_drawing_area_new();
843     gtk_widget_set_size_request(state, 20, 20);
844     /* Labels will notice clicks by default, but drawing areas need to
845      * be told to. */
846     gtk_widget_add_events(state, GDK_BUTTON_PRESS_MASK);
847     gtk_container_add(GTK_CONTAINER(grid), header);
848     gtk_container_add(GTK_CONTAINER(grid), counter);
849     gtk_container_add(GTK_CONTAINER(grid), state);
850     g_signal_connect(state, "draw", G_CALLBACK(draw_tape_icon_cb), GINT_TO_POINTER(0));
851     return grid;
852 }
853 
854 /** \brief Alter widget visibility within the joyport widget so that
855  *         only currently existing joystick ports are displayed.
856  *
857  *  It is safe to call this routine regularly, as it will only trigger
858  *  UI refresh operations if the configuration has changed to no
859  *  longer match the current layout. */
vice_gtk3_update_joyport_layout(void)860 static void vice_gtk3_update_joyport_layout(void)
861 {
862     int i, ok[JOYPORT_MAX_PORTS];
863     int userport_joysticks = 0;
864     int new_joyport_mask = 0;
865     /* Start with all ports enabled */
866     for (i = 0; i < JOYPORT_MAX_PORTS; ++i) {
867         ok[i] = 1;
868     }
869     /* Check for userport joystick counts */
870     if ((machine_class != VICE_MACHINE_CBM5x0) &&
871             (machine_class != VICE_MACHINE_VSID)) {
872         int upjoy = 0;
873         resources_get_int("UserportJoy", &upjoy);
874         if (upjoy) {
875             ++userport_joysticks;
876         }
877         if (machine_class != VICE_MACHINE_C64DTV) {
878             int uptype = USERPORT_JOYSTICK_HUMMER;
879             resources_get_int("UserportJoyType", &uptype);
880             if ((uptype != USERPORT_JOYSTICK_HUMMER) &&
881                 (uptype != USERPORT_JOYSTICK_OEM) &&
882                 (upjoy != 0)) {
883                 ++userport_joysticks;
884             }
885         }
886 
887     }
888     /* Port 1 disabled for machines that have no internal joystick
889      * ports */
890     if ((machine_class == VICE_MACHINE_CBM6x0) ||
891         (machine_class == VICE_MACHINE_PET) ||
892         (machine_class == VICE_MACHINE_VSID)) {
893         ok[0] = 0;
894     }
895     /* Port 2 disabled for machines that have at most one internal
896      * joystick ports */
897     if ((machine_class == VICE_MACHINE_VIC20) ||
898         (machine_class == VICE_MACHINE_CBM6x0) ||
899         (machine_class == VICE_MACHINE_PET) ||
900         (machine_class == VICE_MACHINE_VSID)) {
901         ok[1] = 0;
902     }
903     /* Port 3 disabled for machines with no user port and no other
904      * joystick adapter type, or where no userport joystick is
905      * configured */
906     if ((machine_class == VICE_MACHINE_CBM5x0) || (userport_joysticks < 1)) {
907         ok[2] = 0;
908     }
909     /* Port 4 disabled for machines with no user port, or not enough
910      * userport lines for 2 port userport adapters, or where at most
911      * one userport joystick is configured */
912     if ((machine_class == VICE_MACHINE_CBM5x0) ||
913         (machine_class == VICE_MACHINE_C64DTV) ||
914         (userport_joysticks < 2)) {
915         ok[3] = 0;
916     }
917     /* Port 5 disabled for machines with no 5th control port,  */
918     if (machine_class != VICE_MACHINE_PLUS4) {
919         ok[4] = 0;
920     } else {
921         /* Port 5 also disabled if there's no SID joystick configured */
922         int sidjoy = 0;
923         resources_get_int("SIDCartJoy", &sidjoy);
924         if (!sidjoy) {
925             ok[4] = 0;
926         }
927     }
928     /* Now that we have a list of disabled/enabled ports, let's check
929      * to see if anything has changed */
930     for (i = 0; i < JOYPORT_MAX_PORTS; ++i) {
931         new_joyport_mask <<= 1;
932         if (ok[i]) {
933             new_joyport_mask |= 1;
934         }
935     }
936     if (new_joyport_mask != sb_state.joyports_enabled) {
937         int j;
938         sb_state.joyports_enabled = new_joyport_mask;
939         for (j = 0; j < MAX_STATUS_BARS; ++j) {
940             GtkWidget *joyports_grid;
941 
942             if (allocated_bars[j].joysticks == NULL) {
943                 continue;
944             }
945             joyports_grid =  gtk_bin_get_child(
946                     GTK_BIN(allocated_bars[j].joysticks));
947 
948             /* Hide and show the joystick ports as required */
949             for (i = 0; i < JOYPORT_MAX_PORTS; ++i) {
950                 GtkWidget *child = gtk_grid_get_child_at(GTK_GRID(joyports_grid), 1+i, 0);
951                 if (child) {
952                     if (ok[i]) {
953                         gtk_widget_set_no_show_all(child, FALSE);
954                         gtk_widget_show_all(child);
955                     } else {
956                         gtk_widget_set_no_show_all(child, TRUE);
957                         gtk_widget_hide(child);
958                     }
959                 }
960             }
961         }
962     }
963 }
964 
965 
966 /** \brief  Cursor used when hovering over the joysticks widgets
967  *
968  * TODO:    figure out if I need to clean this up or that Gtk will?
969  */
970 static GdkCursor *joywidget_mouse_ptr = NULL;
971 
972 
973 /** \brief  Handler for the enter/leave-notify events of the joysticks widget
974  *
975  * \param[in]   widget      widget triggering the event
976  * \param[in]   event       event reference
977  * \param[in]   user_data   extra event data (unused)
978  *
979  * \return  bool    (FALSE = keep propagating event, TRUE = stop)
980  */
on_joystick_widget_hover(GtkWidget * widget,GdkEvent * event,gpointer user_data)981 static gboolean on_joystick_widget_hover(GtkWidget *widget, GdkEvent *event,
982                                          gpointer user_data)
983 {
984     if (event != NULL) {
985         GdkDisplay *display = gtk_widget_get_display(widget);
986         GdkWindow *window = gtk_widget_get_window(widget);
987         GdkCursor *cursor;
988 
989         if (display == NULL) {
990             debug_gtk3("failed to retrieve GdkDisplay.");
991             return FALSE;
992         }
993         if (window == NULL) {
994             debug_gtk3("failed to retrieve GdkWindow.");
995             return FALSE;
996         }
997 
998         if (event->type == GDK_ENTER_NOTIFY) {
999             debug_gtk3("Entered joystick widget area.");
1000             if (joywidget_mouse_ptr == NULL) {
1001                 joywidget_mouse_ptr = gdk_cursor_new_from_name(display, "pointer");
1002             }
1003             cursor = joywidget_mouse_ptr;
1004 
1005         } else {
1006             debug_gtk3("Left joystick widget area.");
1007             cursor = NULL;
1008         }
1009         gdk_window_set_cursor(window, cursor);
1010     }
1011     return FALSE;
1012 }
1013 
1014 
1015 /** \brief  Handler for button-press events of the joysticks widget
1016  *
1017  * \param[in]   widget      widget triggering the event
1018  * \param[in]   event       event reference
1019  * \param[in]   user_data   extra event data (unused)
1020  *
1021  * \return  TRUE to stop other handlers, FALSE to propagate event further
1022  */
on_joystick_widget_button_press(GtkWidget * widget,GdkEvent * event,gpointer user_data)1023 static gboolean on_joystick_widget_button_press(GtkWidget *widget,
1024                                                 GdkEvent *event,
1025                                                 gpointer user_data)
1026 {
1027     debug_gtk3("Got button click");
1028 
1029     if (((GdkEventButton *)event)->button == GDK_BUTTON_PRIMARY) {
1030         GtkWidget *menu = joystick_menu_popup_create();
1031         gtk_menu_popup_at_widget(GTK_MENU(menu), widget,
1032                 GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST,
1033                 event);
1034         return TRUE;
1035     }
1036     return FALSE;
1037 }
1038 
1039 
1040 
1041 
1042 /** \brief Create a master joyport widget for inclusion in the status
1043  *         bar.
1044  *
1045  *  Individual joyport representations are part of this widget and
1046  *  update functions will index the GtkGrid in the master widget to
1047  *  reach them.
1048  *
1049  *  \return The constructed widget. This widget will be a floating
1050  *          reference.
1051  */
ui_joystick_widget_create(void)1052 static GtkWidget *ui_joystick_widget_create(void)
1053 {
1054     GtkWidget *grid;
1055     GtkWidget *label;
1056     GtkWidget *event_box;
1057     int i;
1058 
1059     grid = gtk_grid_new();
1060     gtk_orientable_set_orientation(GTK_ORIENTABLE(grid), GTK_ORIENTATION_HORIZONTAL);
1061     gtk_widget_set_hexpand(grid, FALSE);
1062     label = gtk_label_new("Joysticks:");
1063     gtk_widget_set_halign(label, GTK_ALIGN_START);
1064     gtk_widget_set_hexpand(label, TRUE);
1065     gtk_container_add(GTK_CONTAINER(grid), label);
1066     /* Create all possible joystick displays */
1067     for (i = 0; i < JOYPORT_MAX_PORTS; ++i) {
1068         GtkWidget *joyport = gtk_drawing_area_new();
1069         /* add events it should respond to */
1070         gtk_widget_add_events(joyport,
1071                 GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK);
1072         gtk_widget_set_size_request(joyport,20,20);
1073         gtk_container_add(GTK_CONTAINER(grid), joyport);
1074         g_signal_connect(joyport, "draw", G_CALLBACK(draw_joyport_cb), GINT_TO_POINTER(i));
1075         gtk_widget_set_no_show_all(joyport, TRUE);
1076         gtk_widget_hide(joyport);
1077     }
1078 
1079     /*
1080      * Pack the joystick grid into an event box so we can have a popup menu and
1081      * also change the cursor shape to indicate to the user the joystick widget
1082      * is clickable.
1083      */
1084     event_box = gtk_event_box_new();
1085     gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
1086     gtk_container_add(GTK_CONTAINER(event_box), grid);
1087 
1088 
1089 
1090     g_signal_connect(event_box, "button-press-event",
1091             G_CALLBACK(on_joystick_widget_button_press), NULL);
1092     g_signal_connect(event_box, "enter-notify-event",
1093             G_CALLBACK(on_joystick_widget_hover), NULL);
1094     g_signal_connect(event_box, "leave-notify-event",
1095             G_CALLBACK(on_joystick_widget_hover), NULL);
1096 
1097     return event_box;
1098 }
1099 
1100 /** Event handler for hovering over a clickable part of the status bar.
1101  *
1102  *  This will switch to or from the "hand" cursor as needed, creating
1103  *  it if necessary.
1104  *
1105  *  \param widget    The widget firing the event
1106  *  \param event     The GdkEventCross that caused the callback
1107  *  \param user_data The ui_statusbar_t object containing widget
1108  *
1109  *  \return TRUE if further event processing should be blocked.
1110  */
ui_statusbar_cross_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)1111 static gboolean ui_statusbar_cross_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1112 {
1113     ui_statusbar_t *sb = (ui_statusbar_t *)user_data;
1114     if (event && event->type == GDK_ENTER_NOTIFY) {
1115         GdkDisplay *display;
1116         /* Sanity check arguments */
1117         if (sb == NULL) {
1118             /* Should be impossible */
1119             return FALSE;
1120         }
1121         /* If the "hand" pointer hasn't been created yet, create it */
1122         display = gtk_widget_get_display(widget);
1123         if (display != NULL && sb->hand_ptr == NULL) {
1124             sb->hand_ptr = gdk_cursor_new_from_name(display, "pointer");
1125             if (sb->hand_ptr != NULL) {
1126                 g_object_ref_sink(G_OBJECT(sb->hand_ptr));
1127             } else {
1128                 fprintf(stderr, "GTK3 CURSOR: Could not allocate custom pointer for status bar\n");
1129             }
1130         }
1131         /* If the "hand" pointer is OK, use it */
1132         if (sb->hand_ptr != NULL) {
1133             GdkWindow *window = gtk_widget_get_window(widget);
1134             if (window) {
1135                 gdk_window_set_cursor(window, sb->hand_ptr);
1136             }
1137         }
1138     } else {
1139         /* We're leaving the target widget, so change the pointer back
1140          * to default */
1141         GdkWindow *window = gtk_widget_get_window(widget);
1142         if (window) {
1143             gdk_window_set_cursor(window, NULL);
1144         }
1145     }
1146     return FALSE;
1147 }
1148 
1149 /** \brief Lay out the disk drive widgets inside a status bar.
1150  *
1151  *  Depending on which drives are enabled, any given drive may appear
1152  *  on different columns or even rows. This routine handles that flow
1153  *  as the configuration changes.
1154  *
1155  *  \param bar_index Which status bar to lay out.
1156  */
layout_statusbar_drives(int bar_index)1157 static void layout_statusbar_drives(int bar_index)
1158 {
1159     int i, j, state, tde = 0;
1160     int enabled_drive_index = 0;
1161     GtkWidget *bar = allocated_bars[bar_index].bar;
1162     if (!bar) {
1163         return;
1164     }
1165     /* Delete all the drives and dividers that may exist. WARNING:
1166      * This code assumes that the drive widgets are the rightmost
1167      * elements of the status bar. */
1168     for (i = 0; i < ((DRIVE_NUM + 1) / 2) * 2; ++i) {
1169         for (j = 0; j < 2; ++j) {
1170             GtkWidget *child = gtk_grid_get_child_at(GTK_GRID(bar), 6+i, j);
1171             if (child) {
1172                 /* Fun GTK3 fact! If you destroy an event box, then
1173                  * even if the thing it contains still has references
1174                  * left, that child is destroyed _anyway_. To avoid
1175                  * this tragic eventuality, we detach the child files
1176                  * before removing the box. */
1177                 /* TODO: This implies that we really should not be
1178                  * relying on g_object_ref to preserve objects, and
1179                  * instead keep track of widget indices the hard
1180                  * way. */
1181                 if (GTK_IS_EVENT_BOX(child)) {
1182                     GtkWidget *grandchild = gtk_bin_get_child(GTK_BIN(child));
1183                     gtk_container_remove(GTK_CONTAINER(child), grandchild);
1184                 }
1185                 gtk_container_remove(GTK_CONTAINER(bar), child);
1186             }
1187         }
1188     }
1189     state = sb_state.drives_enabled;
1190     tde = sb_state.drives_tde_enabled;
1191     for (i = 0; i < DRIVE_NUM; ++i) {
1192         if (state & 1) {
1193             GtkWidget *drive = allocated_bars[bar_index].drives[i];
1194             GtkWidget *event_box = gtk_event_box_new();
1195             int row = enabled_drive_index % 2;
1196             int column = (enabled_drive_index / 2) * 2 + SB_COL_DRIVE;
1197             if (row == 0) {
1198                 gtk_grid_attach(GTK_GRID(bar),
1199                         gtk_separator_new(GTK_ORIENTATION_VERTICAL),
1200                         column - 1, 0, 1, 2);
1201             }
1202             gtk_container_add(GTK_CONTAINER(event_box), drive);
1203             gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
1204             g_signal_connect(event_box, "button-press-event", G_CALLBACK(ui_do_drive_popup), GINT_TO_POINTER(i));
1205             g_signal_connect(event_box, "enter-notify-event", G_CALLBACK(ui_statusbar_cross_cb), &allocated_bars[i]);
1206             g_signal_connect(event_box, "leave-notify-event", G_CALLBACK(ui_statusbar_cross_cb), &allocated_bars[i]);
1207             gtk_widget_show_all(event_box);
1208             if (tde & 1) {
1209                 gtk_widget_show(gtk_grid_get_child_at(GTK_GRID(drive), 2, 0));
1210             } else {
1211                 gtk_widget_hide(gtk_grid_get_child_at(GTK_GRID(drive), 2, 0));
1212             }
1213             gtk_grid_attach(GTK_GRID(bar), event_box, column, row, 1, 1);
1214             ++enabled_drive_index;
1215         }
1216         state >>= 1;
1217         tde >>= 1;
1218     }
1219     gtk_widget_show_all(bar);
1220 }
1221 
1222 /** Widget destruction callback for status bars.
1223  *
1224  * \param sb      The status bar being destroyed. This should be
1225  *                registered in some ui_statusbar_t structure as the
1226  *                bar field.
1227  * \param ignored User data pointer mandated by GTK. Unused.
1228  */
destroy_statusbar_cb(GtkWidget * sb,gpointer ignored)1229 static void destroy_statusbar_cb(GtkWidget *sb, gpointer ignored)
1230 {
1231     int i, j;
1232 
1233     for (i = 0; i < MAX_STATUS_BARS; ++i) {
1234         if (allocated_bars[i].bar == sb) {
1235             allocated_bars[i].bar = NULL;
1236 #if 0
1237             if (allocated_bars[i].msg) {
1238                 g_object_unref(G_OBJECT(allocated_bars[i].msg));
1239                 allocated_bars[i].msg = NULL;
1240             }
1241 #endif
1242             if (allocated_bars[i].speed != NULL) {
1243                 g_object_unref(G_OBJECT(allocated_bars[i].speed));
1244                 allocated_bars[i].speed = NULL;
1245             }
1246 
1247             if (allocated_bars[i].crt != NULL) {
1248                 g_object_unref(G_OBJECT(allocated_bars[i].crt));
1249                 allocated_bars[i].crt = NULL;
1250             }
1251             if (allocated_bars[i].mixer != NULL) {
1252                 g_object_unref(G_OBJECT(allocated_bars[i].mixer));
1253                 allocated_bars[i].mixer = NULL;
1254             }
1255             if (allocated_bars[i].tape) {
1256                 g_object_unref(G_OBJECT(allocated_bars[i].tape));
1257                 allocated_bars[i].tape = NULL;
1258             }
1259             if (allocated_bars[i].tape_menu) {
1260                 g_object_unref(G_OBJECT(allocated_bars[i].tape_menu));
1261                 allocated_bars[i].tape_menu = NULL;
1262             }
1263             if (allocated_bars[i].joysticks) {
1264                 g_object_unref(G_OBJECT(allocated_bars[i].joysticks));
1265                 allocated_bars[i].joysticks = NULL;
1266             }
1267             for (j = 0; j < DRIVE_NUM; ++j) {
1268                 if (allocated_bars[i].drives[j]) {
1269                     g_object_unref(G_OBJECT(allocated_bars[i].drives[j]));
1270                     g_object_unref(G_OBJECT(allocated_bars[i].drive_popups[j]));
1271                     allocated_bars[i].drives[j] = NULL;
1272                     allocated_bars[i].drive_popups[j] = NULL;
1273                 }
1274             }
1275             if (allocated_bars[i].hand_ptr) {
1276                 g_object_unref(G_OBJECT(allocated_bars[i].hand_ptr));
1277                 allocated_bars[i].hand_ptr = NULL;
1278             }
1279         }
1280     }
1281 }
1282 
1283 
1284 /** \brief  Handler for the 'toggled' event of the CRT controls checkbox
1285  *
1286  * Toggles the display state of the CRT controls
1287  *
1288  * \param[in]   widget  checkbox triggering the event
1289  * \param[in]   data    extra event data (unused
1290  */
on_crt_toggled(GtkWidget * widget,gpointer data)1291 static void on_crt_toggled(GtkWidget *widget, gpointer data)
1292 {
1293     gboolean state;
1294 
1295     state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
1296     ui_enable_crt_controls((gboolean)state);
1297 }
1298 
1299 
1300 /** \brief  Handler for the 'toggled' event of the mixer controls checkbox
1301  *
1302  * Toggles the display state of the mixer controls
1303  *
1304  * \param[in]   widget  checkbox triggering the event
1305  * \param[in]   data    extra event data (unused
1306  */
on_mixer_toggled(GtkWidget * widget,gpointer data)1307 static void on_mixer_toggled(GtkWidget *widget, gpointer data)
1308 {
1309     gboolean state;
1310 
1311     state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
1312     ui_enable_mixer_controls((gboolean)state);
1313 }
1314 
1315 
1316 
1317 /** \brief Create a popup menu to attach to a disk drive widget.
1318  *
1319  *  \param unit The index of the drive, 0-3 for drives 8-11.
1320  *
1321  *  \return The GtkMenu for use as a popup, as a floating reference.
1322  */
ui_drive_menu_create(int unit)1323 static GtkWidget *ui_drive_menu_create(int unit)
1324 {
1325     char buf[128];
1326     GtkWidget *drive_menu = gtk_menu_new();
1327     GtkWidget *drive_menu_item;
1328 
1329     snprintf(buf, 128, "Attach disk to drive #%d...", unit + 8);
1330     buf[127] = 0;
1331 
1332     drive_menu_item = gtk_menu_item_new_with_label(buf);
1333     g_signal_connect(drive_menu_item, "activate",
1334             G_CALLBACK(ui_disk_attach_callback), GINT_TO_POINTER(unit + 8));
1335     gtk_container_add(GTK_CONTAINER(drive_menu), drive_menu_item);
1336     snprintf(buf, 128, "Detach disk from drive #%d", unit + 8);
1337     buf[127] = 0;
1338     drive_menu_item = gtk_menu_item_new_with_label(buf);
1339     g_signal_connect(drive_menu_item, "activate",
1340             G_CALLBACK(ui_disk_detach_callback), GINT_TO_POINTER(unit + 8));
1341     gtk_container_add(GTK_CONTAINER(drive_menu), drive_menu_item);
1342     /* GTK2/GNOME UI put TDE and Read-only checkboxes here, but that
1343      * seems excessive or possibly too fine-grained, so skip that for
1344      * now */
1345     ui_populate_fliplist_menu(drive_menu, unit + 8, 0);
1346 
1347 
1348     gtk_container_add(GTK_CONTAINER(drive_menu),
1349             gtk_separator_menu_item_new());
1350     drive_menu_item = gtk_menu_item_new_with_label("Configure drives ...");
1351     g_signal_connect(drive_menu_item, "activate",
1352             G_CALLBACK(on_drive_configure_activate), NULL);
1353     gtk_container_add(GTK_CONTAINER(drive_menu), drive_menu_item);
1354 
1355     gtk_widget_show_all(drive_menu);
1356     return drive_menu;
1357 }
1358 
1359 /** \brief Create a new status bar.
1360  *
1361  *  This function should be called once as part of creating a new
1362  *  machine window.
1363  *
1364  *  \return A new status bar, as a floating reference, or NULL if all
1365  *          possible status bars have been allocated already.
1366  */
ui_statusbar_create(void)1367 GtkWidget *ui_statusbar_create(void)
1368 {
1369     GtkWidget *sb, *speed, *tape, *tape_events, *joysticks;
1370     GtkWidget *crt = NULL;
1371     GtkWidget *mixer = NULL;
1372     int i, j;
1373 
1374     for (i = 0; i < MAX_STATUS_BARS; ++i) {
1375         if (allocated_bars[i].bar == NULL) {
1376             break;
1377         }
1378     }
1379 
1380     if (i >= MAX_STATUS_BARS) {
1381         /* Fatal error? */
1382         return NULL;
1383     }
1384 
1385     /* While the status bar itself is returned floating, we sink all
1386      * of its information-bearing subwidgets. This is so that we can
1387      * remove or add them to the status bar as the configuration
1388      * demands, while ensuring they remain alive. They receive an
1389      * extra dereference in ui_statusbar_destroy() so nothing should
1390      * leak. */
1391     sb = vice_gtk3_grid_new_spaced(8, 0);
1392 
1393     /* First column: CPU/FPS */
1394 
1395     speed = statusbar_speed_widget_create();
1396     g_object_ref_sink(G_OBJECT(speed));
1397     g_object_set(speed, "margin-left", 8, NULL);
1398 
1399     /* don't add CRT or Mixer controls when VSID */
1400     if (machine_class != VICE_MACHINE_VSID) {
1401         crt = gtk_check_button_new_with_label("CRT controls");
1402         gtk_widget_set_can_focus(crt, FALSE);
1403         g_object_ref_sink(G_OBJECT(crt));
1404         gtk_widget_set_halign(crt, GTK_ALIGN_START);
1405         gtk_widget_show_all(crt);
1406         g_signal_connect(crt, "toggled", G_CALLBACK(on_crt_toggled), NULL);
1407 
1408         mixer = gtk_check_button_new_with_label("Mixer controls");
1409         gtk_widget_set_can_focus(mixer, FALSE);
1410         g_object_ref_sink(G_OBJECT(mixer));
1411         gtk_widget_set_halign(mixer, GTK_ALIGN_START);
1412         gtk_widget_show_all(mixer);
1413         g_signal_connect(mixer, "toggled", G_CALLBACK(on_mixer_toggled), NULL);
1414     }
1415 
1416     g_signal_connect(sb, "destroy", G_CALLBACK(destroy_statusbar_cb), NULL);
1417     allocated_bars[i].bar = sb;
1418     allocated_bars[i].speed = speed;
1419     gtk_grid_attach(GTK_GRID(sb), speed, SB_COL_SPEED, 0, 1, 2);
1420 
1421     /* Second column: separator */
1422     gtk_grid_attach(GTK_GRID(sb), gtk_separator_new(GTK_ORIENTATION_VERTICAL),
1423             SB_COL_SEP_MSG, 0, 1, 2);
1424 
1425     /* TODO: skip VSID and add another separator after the checkbox */
1426     if (machine_class != VICE_MACHINE_VSID) {
1427         allocated_bars[i].crt = crt;
1428         gtk_grid_attach(GTK_GRID(sb), crt, SB_COL_CRT, 0, 1, 1);
1429         allocated_bars[i].mixer = mixer;
1430         gtk_grid_attach(GTK_GRID(sb), mixer, SB_COL_CRT, 1, 1, 1);
1431         /* add separator */
1432         gtk_grid_attach(GTK_GRID(sb), gtk_separator_new(GTK_ORIENTATION_VERTICAL),
1433                 SB_COL_SEP_CRT, 0, 1, 2);
1434     }
1435 
1436     if ((machine_class != VICE_MACHINE_C64DTV)
1437             && (machine_class != VICE_MACHINE_VSID)) {
1438         tape = ui_tape_widget_create();
1439         g_object_ref_sink(G_OBJECT(tape));
1440         /* Clicking the tape status is supposed to pop up a window. This
1441          * requires a way to make sure events are captured by random
1442          * internal widgets; the GtkEventBox manages that task for us. */
1443         tape_events = gtk_event_box_new();
1444         gtk_event_box_set_visible_window(GTK_EVENT_BOX(tape_events), FALSE);
1445         gtk_container_add(GTK_CONTAINER(tape_events), tape);
1446         gtk_grid_attach(GTK_GRID(sb), tape_events, SB_COL_TAPE, 0, 1, 1);
1447         allocated_bars[i].tape = tape;
1448         allocated_bars[i].tape_menu = ui_create_datasette_control_menu();
1449         g_object_ref_sink(G_OBJECT(allocated_bars[i].tape_menu));
1450         g_signal_connect(tape_events, "button-press-event",
1451                 G_CALLBACK(ui_do_datasette_popup), GINT_TO_POINTER(i));
1452         g_signal_connect(tape_events, "enter-notify-event",
1453                 G_CALLBACK(ui_statusbar_cross_cb), &allocated_bars[i]);
1454         g_signal_connect(tape_events, "leave-notify-event",
1455                 G_CALLBACK(ui_statusbar_cross_cb), &allocated_bars[i]);
1456     }
1457 
1458     if (machine_class != VICE_MACHINE_VSID) {
1459         joysticks = ui_joystick_widget_create();
1460         g_object_ref(joysticks);
1461         gtk_widget_set_halign(joysticks, GTK_ALIGN_END);
1462         gtk_grid_attach(GTK_GRID(sb), joysticks, SB_COL_TAPE, 1, 1, 1);
1463         allocated_bars[i].joysticks = joysticks;
1464     }
1465 
1466     /* Third column on: Drives. */
1467     for (j = 0; j < DRIVE_NUM; ++j) {
1468         GtkWidget *drive = ui_drive_widget_create(j);
1469         GtkWidget *drive_menu = ui_drive_menu_create(j);
1470         g_object_ref_sink(G_OBJECT(drive));
1471         g_object_ref_sink(G_OBJECT(drive_menu));
1472         allocated_bars[i].drives[j] = drive;
1473         allocated_bars[i].drive_popups[j] = drive_menu;
1474     }
1475     /* WARNING: The current implementation of ui_enable_drive_status()
1476      * relies on the fact that the drives are the last elements of the
1477      * statusbar display. If more widgets are added past this point,
1478      * that function will need to change as well. */
1479     layout_statusbar_drives(i);
1480 
1481     /* Set an impossible number of joyports to enabled so that the status
1482      * is guarenteed to be updated. */
1483     sb_state.joyports_enabled = ~0;
1484     vice_gtk3_update_joyport_layout();
1485     return sb;
1486 }
1487 
1488 /** \brief Statusbar API function to register an elapsed time.
1489  *
1490  *  \param current The current time value.
1491  *  \param total   The maximum time value
1492  *
1493  *  \todo This function is not implemented and its API is not
1494  *        understood.
1495  *
1496  * \note    Since the statusbar message display widget has been removed, we
1497  *          have some space to implement a widget to display information
1498  *          regarding playback/recording.
1499  */
ui_display_event_time(unsigned int current,unsigned int total)1500 void ui_display_event_time(unsigned int current, unsigned int total)
1501 {
1502     NOT_IMPLEMENTED_WARN_ONLY();
1503 }
1504 
1505 /** \brief Statusbar API function to display playback status.
1506  *
1507  *  \param playback_status Unknown.
1508  *  \param version         Unknown.
1509  *
1510  *  \todo This function is not implemented and its API is not
1511  *        understood.
1512  *
1513  * \note    Since the statusbar message display widget has been removed, we
1514  *          have some space to implement a widget to display information
1515  *          regarding playback/recording.
1516  */
ui_display_playback(int playback_status,char * version)1517 void ui_display_playback(int playback_status, char *version)
1518 {
1519     NOT_IMPLEMENTED_WARN_ONLY();
1520 }
1521 
1522 /** \brief Statusbar API function to display recording status.
1523  *
1524  *  \param recording_status Unknown.
1525  *
1526  *  \todo This function is not implemented and its API is not
1527  *        understood.
1528  *
1529  * \note    Since the statusbar message display widget has been removed, we
1530  *          have some space to implement a widget to display information
1531  *          regarding playback/recording.
1532  */
ui_display_recording(int recording_status)1533 void ui_display_recording(int recording_status)
1534 {
1535     NOT_IMPLEMENTED_WARN_ONLY();
1536 }
1537 
1538 #if 0
1539 /** \brief Directly and unconditionally set the status bar message text.
1540  *
1541  *  \param text The text to display in the status bar.
1542  */
1543 static void
1544 display_statustext_internal(const char *text)
1545 {
1546 #if 0
1547     int i;
1548     for (i = 0; i < MAX_STATUS_BARS; ++i) {
1549         if (allocated_bars[i].msg) {
1550             gtk_label_set_text(allocated_bars[i].msg, text);
1551         }
1552     }
1553 #endif
1554 }
1555 #endif
1556 
1557 #if 0
1558 /** \brief Timeout callback for messages in the status bar.
1559  *
1560  *  If the message ID associated with this callback matches the
1561  *  currently displayed message, erases the current message.
1562  *
1563  *  \param data The message ID for this callback.
1564  *  \return TRUE if processing of the timeout callback should not
1565  *          propagate.
1566  *  \sa ui_sb_state_s::statustext_msgid
1567  */
1568 static gboolean ui_statustext_fadeout(gpointer data)
1569 {
1570     intptr_t my_id = GPOINTER_TO_INT(data);
1571     if (my_id == sb_state.statustext_msgid) {
1572         display_statustext_internal("");
1573     }
1574     return FALSE;
1575 }
1576 #endif
1577 
1578 /** \brief Statusbar API function to display a message in the status bar.
1579  *
1580  *  \param text     The text to display.
1581  *  \param fade_out If nonzero, erase the text after five seconds
1582  *                  unless it has already been replaced.
1583  */
ui_display_statustext(const char * text,int fade_out)1584 void ui_display_statustext(const char *text, int fade_out)
1585 {
1586     /*
1587      * No longer used, but needs to remain here since it's called from
1588      * src/network.c
1589      */
1590 #if 0
1591     ++sb_state.statustext_msgid;
1592     display_statustext_internal(text);
1593     if (fade_out) {
1594         g_timeout_add(5000, ui_statustext_fadeout,
1595                 GINT_TO_POINTER(sb_state.statustext_msgid));
1596     }
1597 #endif
1598 }
1599 
1600 /** \brief Statusbar API function to display current volume
1601  *
1602  * This function is a NOP since the volume can be checked and altered via the
1603  * Mixer Controls via the statusbar.
1604  *
1605  * \param[in]   vol     new volume level
1606  */
ui_display_volume(int vol)1607 void ui_display_volume(int vol)
1608 {
1609     /* NOP */
1610 }
1611 
1612 
1613 /** \brief Statusbar API function to display current joyport inputs.
1614  *  \param joyport An array of bytes of size at least
1615  *                 JOYPORT_MAX_PORTS+1, with data regarding each
1616  *                 active joyport.
1617  *  \warning The joyport array is, for all practical purposes,
1618  *           _1-indexed_. joyport[0] is unused.
1619  *  \sa ui_sb_state_s::current_joyports Describes the format of the
1620  *      data encoded in the joyport array. Note that current_joyports
1621  *      is 0-indexed as is typical for C arrays.
1622  */
ui_display_joyport(uint8_t * joyport)1623 void ui_display_joyport(uint8_t *joyport)
1624 {
1625     int i;
1626     for (i = 0; i < JOYPORT_MAX_PORTS; ++i) {
1627         /* Compare the new value to the current one, set the new
1628          * value, and queue a redraw if and only if there was a
1629          * change. And yes, the input joystick ports are 1-indexed. I
1630          * don't know either. */
1631         if (sb_state.current_joyports[i] != joyport[i+1]) {
1632             int j;
1633             sb_state.current_joyports[i] = joyport[i+1];
1634             for (j = 0; j < MAX_STATUS_BARS; ++j) {
1635                 if (allocated_bars[j].joysticks) {
1636                     GtkWidget *grid;
1637                     GtkWidget *widget;
1638 
1639                     grid = gtk_bin_get_child(GTK_BIN(allocated_bars[j].joysticks));
1640                     widget = gtk_grid_get_child_at(GTK_GRID(grid), i + 1, 0);
1641                     if (widget) {
1642                         gtk_widget_queue_draw(widget);
1643                     }
1644                 }
1645             }
1646         }
1647     }
1648     /* Restrict visible joystick display to just the ones the
1649      * configuration supports */
1650     vice_gtk3_update_joyport_layout();
1651 }
1652 
1653 /** \brief Statusbar API function to report changes in tape control
1654  *         status.
1655  *
1656  *  \param control The new tape control. See the DATASETTE_CONTROL_*
1657  *                 constants in datasette.h for legal values of this
1658  *                 parameter.
1659  */
ui_display_tape_control_status(int control)1660 void ui_display_tape_control_status(int control)
1661 {
1662     if (control != sb_state.tape_control) {
1663         int i;
1664         sb_state.tape_control = control;
1665         for (i = 0; i < MAX_STATUS_BARS; ++i) {
1666             if (allocated_bars[i].tape) {
1667                 GtkWidget *widget = gtk_grid_get_child_at(GTK_GRID(allocated_bars[i].tape), 2, 0);
1668                 if (widget) {
1669                     gtk_widget_queue_draw(widget);
1670                 }
1671             }
1672         }
1673     }
1674 }
1675 
1676 /** \brief Statusbar API function to report changes in tape position.
1677  *
1678  *  \param counter The new value of the position counter.
1679  *
1680  *  \note Only the last three digits of the counter will be displayed.
1681  */
ui_display_tape_counter(int counter)1682 void ui_display_tape_counter(int counter)
1683 {
1684     if (counter != sb_state.tape_counter) {
1685         int i;
1686         char buf[8];
1687         snprintf(buf, 8, "%03d", counter%1000);
1688         buf[7] = 0;
1689         sb_state.tape_counter = counter;
1690         for (i = 0; i < MAX_STATUS_BARS; ++i) {
1691             if (allocated_bars[i].tape) {
1692                 GtkWidget *widget = gtk_grid_get_child_at(GTK_GRID(allocated_bars[i].tape), 1, 0);
1693                 if (widget) {
1694                     gtk_label_set_text(GTK_LABEL(widget), buf);
1695                 }
1696             }
1697         }
1698     }
1699 }
1700 
1701 /** \brief Statusbar API function to report changes in the tape motor.
1702  *
1703  *  \param motor Nonzero if the tape motor is now on.
1704  */
ui_display_tape_motor_status(int motor)1705 void ui_display_tape_motor_status(int motor)
1706 {
1707     if (motor != sb_state.tape_motor_status) {
1708         int i;
1709         sb_state.tape_motor_status = motor;
1710         for (i = 0; i < MAX_STATUS_BARS; ++i) {
1711             if (allocated_bars[i].tape) {
1712                 GtkWidget *widget = gtk_grid_get_child_at(GTK_GRID(allocated_bars[i].tape), 2, 0);
1713                 if (widget) {
1714                     gtk_widget_queue_draw(widget);
1715                 }
1716             }
1717         }
1718     }
1719 }
1720 
1721 /** \brief Statusbar API function to report changes in tape status.
1722  *  \param tape_status The new tape status.
1723  *  \note This function does nothing and its API is not
1724  *        understood. Furthermore, no other extant UIs appear to react
1725  *        to this call.
1726  */
ui_set_tape_status(int tape_status)1727 void ui_set_tape_status(int tape_status)
1728 {
1729     /* printf("TAPE DRIVE STATUS: %d\n", tape_status); */
1730 }
1731 
1732 /** \brief Statusbar API function to report mounting or unmounting of
1733  *         a tape image.
1734  *
1735  *  \param image The filename of the tape image (if mounted), or the
1736  *               empty string or NULL (if unmounting).
1737  */
ui_display_tape_current_image(const char * image)1738 void ui_display_tape_current_image(const char *image)
1739 {
1740 #if 0
1741     char buf[256];
1742     if (image && *image) {
1743         snprintf(buf, 256, "Attached %s to tape unit", image);
1744     } else {
1745         snprintf(buf, 256, "Tape unit is empty");
1746     }
1747 
1748     buf[255] = 0;
1749     ui_display_statustext(buf, 1);
1750 #endif
1751 }
1752 
1753 /** \brief Statusbar API function to report changes in drive LED
1754  *         intensity.
1755  *  \param drive_number The unit to update (0-3 for drives 8-11)
1756  *  \param pwm1         The intensity of the first LED (0=off,
1757  *                      1000=maximum intensity)
1758  *  \param led_pwm2     The intensity of the second LED (0=off,
1759  *                      1000=maximum intensity)
1760  *  \todo The statusbar API does not yet support dual-unit disk
1761  *        drives.
1762  */
ui_display_drive_led(int drive_number,unsigned int pwm1,unsigned int led_pwm2)1763 void ui_display_drive_led(int drive_number, unsigned int pwm1, unsigned int led_pwm2)
1764 {
1765     int i;
1766     if (drive_number < 0 || drive_number > DRIVE_NUM-1) {
1767         /* TODO: Fatal error? */
1768         return;
1769     }
1770     sb_state.current_drive_leds[drive_number][0] = pwm1;
1771     sb_state.current_drive_leds[drive_number][1] = led_pwm2;
1772     for (i = 0; i < MAX_STATUS_BARS; ++i) {
1773         if (allocated_bars[i].bar) {
1774             GtkWidget *drive, *led;
1775             drive = allocated_bars[i].drives[drive_number];
1776             led = gtk_grid_get_child_at(GTK_GRID(drive), 2, 0);
1777             if (led) {
1778                 gtk_widget_queue_draw(led);
1779             }
1780         }
1781     }
1782 }
1783 
1784 /** \brief Statusbar API function to report changes in drive head
1785  *         location.
1786  *  \param drive_number      The unit to update (0-3 for drives 8-11)
1787  *  \param drive_base        Currently unused.
1788  *  \param half_track_number Twice the value of the head
1789  *                           location. 18.0 is 36, while 18.5 would be
1790  *                           37.
1791  *  \todo The statusbar API does not yet support dual-unit disk
1792  *        drives. The drive_base argument will likely come into play
1793  *        once it does.
1794  */
ui_display_drive_track(unsigned int drive_number,unsigned int drive_base,unsigned int half_track_number)1795 void ui_display_drive_track(unsigned int drive_number,
1796                             unsigned int drive_base,
1797                             unsigned int half_track_number)
1798 {
1799     int i;
1800     if (drive_number > DRIVE_NUM-1) {
1801         /* TODO: Fatal error? */
1802         return;
1803     }
1804     for (i = 0; i < MAX_STATUS_BARS; ++i) {
1805         if (allocated_bars[i].bar) {
1806             GtkWidget *drive, *track;
1807             drive = allocated_bars[i].drives[drive_number];
1808             track = gtk_grid_get_child_at(GTK_GRID(drive), 1, 0);
1809             if (track) {
1810                 char track_str[16];
1811                 snprintf(track_str, 16, "%.1lf", half_track_number / 2.0);
1812                 track_str[15] = 0;
1813                 gtk_label_set_text(GTK_LABEL(track), track_str);
1814             }
1815         }
1816     }
1817 }
1818 
1819 /** \brief Update information about each drive.
1820  *
1821  *  \param state           A bitmask int, where bits 0-3 indicate
1822  *                         whether or not drives 8-11 respectively are
1823  *                         being emulated carefully enough to provide
1824  *                         LED information.
1825  *  \param drive_led_color An array of size at least DRIVE_NUM that
1826  *                         provides information about the LEDs on this
1827  *                         drive. An element of this array will only
1828  *                         be checked if the corresponding bit in
1829  *                         state is 1.
1830  *  \note Before calling this function, the drive configuration
1831  *        resources (Drive8Type, Drive9Type, etc) should all be set to
1832  *        the values you wish to display.
1833  *  \warning If a drive's LEDs are active when its LED values change,
1834  *           the UI will not reflect the LED type change until the
1835  *           next time the led's values are updated. This should not
1836  *           happen under normal circumstances.
1837  *  \sa compute_drives_enabled_mask() for how this function determines
1838  *      which drives are truly active
1839  *  \sa ui_sb_state_s::drive_led_types for the data in each element of
1840  *      drive_led_color
1841  */
ui_enable_drive_status(ui_drive_enable_t state,int * drive_led_color)1842 void ui_enable_drive_status(ui_drive_enable_t state, int *drive_led_color)
1843 {
1844     int i, enabled;
1845 
1846     /* Update the drive LEDs first, unconditionally. */
1847     enabled = state;
1848     for (i = 0; i < DRIVE_NUM; ++i) {
1849         if (enabled & 1) {
1850             sb_state.drive_led_types[i] = drive_led_color[i];
1851             sb_state.current_drive_leds[i][0] = 0;
1852             sb_state.current_drive_leds[i][1] = 0;
1853         }
1854         enabled >>= 1;
1855     }
1856 
1857     /* Now give enabled its "real" value based on the drive
1858      * definitions. */
1859     enabled = compute_drives_enabled_mask();
1860 
1861     /* Now, if necessary, update the status bar layouts. We won't need
1862      * to do this if the only change was the kind of drives hooked up,
1863      * instead of the number */
1864     if ((state != sb_state.drives_tde_enabled) || (enabled != sb_state.drives_enabled)) {
1865         sb_state.drives_enabled = enabled;
1866         sb_state.drives_tde_enabled = state;
1867         for (i = 0; i < MAX_STATUS_BARS; ++i) {
1868             layout_statusbar_drives(i);
1869         }
1870     }
1871 }
1872 
1873 /** \brief Statusbar API function to report mounting or unmounting of
1874  *         a disk image.
1875  *
1876  *  \param drive_number 0-3 to represent drives at device 8-11.
1877  *  \param image        The filename of the disk image (if mounted),
1878  *                      or the empty string or NULL (if unmounting).
1879  *  \todo This API is insufficient to describe drives with two disk units.
1880  */
ui_display_drive_current_image(unsigned int drive_number,const char * image)1881 void ui_display_drive_current_image(unsigned int drive_number, const char *image)
1882 {
1883 #if 0
1884     char buf[256];
1885     if (image && *image) {
1886         snprintf(buf, 256, "Attached %s to unit %d", image, drive_number + 8);
1887     } else {
1888         snprintf(buf, 256, "Unit %d is empty", drive_number + 8);
1889     }
1890     buf[255] = 0;
1891     ui_display_statustext(buf, 1);
1892 #endif
1893 }
1894 
1895 
1896 /** \brief  Determine if the CRT controls widget is enabled in \a window
1897  *
1898  * \param[in]   window  GtkWindow instance
1899  *
1900  * \return  bool
1901  */
ui_statusbar_crt_controls_enabled(GtkWidget * window)1902 gboolean ui_statusbar_crt_controls_enabled(GtkWidget *window)
1903 {
1904     GtkWidget *bin;
1905     GtkWidget *bar;
1906     GtkWidget *check;
1907     gboolean active;
1908 
1909     debug_gtk3("called.");
1910     bin = gtk_bin_get_child(GTK_BIN(window));
1911     if (bin != NULL) {
1912         bar = gtk_grid_get_child_at(GTK_GRID(bin), 0, 2);  /* FIX */
1913         if (bar != NULL) {
1914             check = gtk_grid_get_child_at(GTK_GRID(bar), SB_COL_CRT, 0);
1915             if (check != NULL) {
1916                 active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check));
1917                 debug_gtk3("CRT controls enabled = %s.",
1918                         active ? "TRUE" : "FALSE");
1919                 return active;
1920             } else {
1921                 debug_gtk3("Couldn't get Checkbox.");
1922             }
1923         } else {
1924             debug_gtk3("Couldn't get statusbar.");
1925         }
1926     } else {
1927         debug_gtk3("Couldn't get BIN.");
1928     }
1929     debug_gtk3("OOPS!");
1930     return FALSE;
1931 }
1932 
1933 
1934 /** \brief  Determine if the mixer controls widget is enabled in \a window
1935  *
1936  * \param[in]   window  GtkWindow instance
1937  *
1938  * \return  bool
1939  */
ui_statusbar_mixer_controls_enabled(GtkWidget * window)1940 gboolean ui_statusbar_mixer_controls_enabled(GtkWidget *window)
1941 {
1942     GtkWidget *bin;
1943     GtkWidget *bar;
1944     GtkWidget *check;
1945     gboolean active;
1946 
1947     debug_gtk3("called.");
1948     bin = gtk_bin_get_child(GTK_BIN(window));
1949     if (bin != NULL) {
1950         bar = gtk_grid_get_child_at(GTK_GRID(bin), 0, 2);  /* FIX */
1951         if (bar != NULL) {
1952             check = gtk_grid_get_child_at(GTK_GRID(bar), SB_COL_CRT, 1);
1953             if (check != NULL) {
1954                 active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check));
1955                 debug_gtk3("Mixer controls enabled = %s.",
1956                         active ? "TRUE" : "FALSE");
1957                 return active;
1958             }
1959         }
1960     }
1961     debug_gtk3("OOPS!");
1962     return FALSE;
1963 }
1964 
1965 
ui_display_speed(float percent,float framerate,int warp_flag)1966 void ui_display_speed(float percent, float framerate, int warp_flag)
1967 {
1968     GtkWidget *speed;
1969 
1970     speed = allocated_bars[0].speed;
1971     if (speed != NULL) {
1972         statusbar_speed_widget_update(speed, percent, framerate, warp_flag);
1973     }
1974     speed = allocated_bars[1].speed;
1975     if (speed != NULL) {
1976         statusbar_speed_widget_update(speed, percent, framerate, warp_flag);
1977     }
1978 }
1979