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