1 /* gtkui.c: GTK routines for dealing with the user interface
2 Copyright (c) 2000-2015 Philip Kendall, Russell Marks, Sergio Baldoví
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 Author contact information:
19
20 E-mail: philip-fuse@shadowmagic.org.uk
21
22 */
23
24 #include <config.h>
25
26 #include <math.h>
27 #include <stdio.h>
28
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtk.h>
31
32 #include <glib.h>
33
34 #include <libspectrum.h>
35
36 #include "debugger/debugger.h"
37 #include "fuse.h"
38 #include "gtkcompat.h"
39 #include "gtkinternals.h"
40 #include "keyboard.h"
41 #include "machine.h"
42 #include "machines/specplus3.h"
43 #include "menu.h"
44 #include "peripherals/ide/simpleide.h"
45 #include "peripherals/ide/zxatasp.h"
46 #include "peripherals/ide/zxcf.h"
47 #include "peripherals/joystick.h"
48 #include "psg.h"
49 #include "rzx.h"
50 #include "screenshot.h"
51 #include "settings.h"
52 #include "snapshot.h"
53 #include "timer/timer.h"
54 #include "ui/ui.h"
55 #include "utils.h"
56
57 /* The main Fuse window */
58 GtkWidget *gtkui_window;
59
60 /* The area into which the screen will be drawn */
61 GtkWidget *gtkui_drawing_area;
62
63 static GtkWidget *menu_bar;
64
65 /* The UIManager used to create the menu bar */
66 GtkUIManager *ui_manager_menu = NULL;
67
68 /* True if we were paused via the Machine/Pause menu item */
69 static int paused = 0;
70
71 /* Structure used by the radio button selection widgets (eg the
72 graphics filter selectors and Machine/Select) */
73 typedef struct gtkui_select_info {
74
75 GtkWidget *dialog;
76 GtkWidget **buttons;
77
78 /* Used by the graphics filter selectors */
79 scaler_available_fn selector;
80 scaler_type selected;
81
82 /* Used by the joystick confirmation */
83 ui_confirm_joystick_t joystick;
84
85 } gtkui_select_info;
86
87 static gboolean gtkui_make_menu(GtkAccelGroup **accel_group,
88 GtkWidget **menu_bar,
89 GtkActionEntry *menu_data,
90 guint menu_data_size);
91
92 static gboolean gtkui_lose_focus( GtkWidget*, GdkEvent*, gpointer );
93 static gboolean gtkui_gain_focus( GtkWidget*, GdkEvent*, gpointer );
94
95 static gboolean gtkui_delete( GtkWidget *widget, GdkEvent *event,
96 gpointer data );
97
98 static void menu_options_filter_done( GtkWidget *widget, gpointer user_data );
99 static void menu_machine_select_done( GtkWidget *widget, gpointer user_data );
100
101 static const GtkTargetEntry drag_types[] =
102 {
103 { (gchar *)"text/uri-list", GTK_TARGET_OTHER_APP, 0 }
104 };
105
gtkui_drag_data_received(GtkWidget * widget GCC_UNUSED,GdkDragContext * drag_context,gint x GCC_UNUSED,gint y GCC_UNUSED,GtkSelectionData * data,guint info GCC_UNUSED,guint timestamp)106 static void gtkui_drag_data_received( GtkWidget *widget GCC_UNUSED,
107 GdkDragContext *drag_context,
108 gint x GCC_UNUSED, gint y GCC_UNUSED,
109 GtkSelectionData *data,
110 guint info GCC_UNUSED, guint timestamp )
111 {
112 static char uri_prefix[] = "file://";
113 char *filename, *selection_filename;
114 const guchar *selection_data, *data_begin, *data_end, *p;
115 gint selection_length;
116
117 selection_length = gtk_selection_data_get_length( data );
118
119 if ( data && selection_length > (gint) sizeof( uri_prefix ) ) {
120 selection_data = gtk_selection_data_get_data( data );
121 data_begin = selection_data + sizeof( uri_prefix ) - 1;
122 data_end = selection_data + selection_length;
123 p = data_begin;
124 do {
125 if ( *p == '\r' || *p == '\n' ) {
126 data_end = p;
127 break;
128 }
129 } while ( p++ != data_end );
130
131 selection_filename = g_strndup( (const gchar *)data_begin,
132 data_end - data_begin );
133
134 filename = g_uri_unescape_string( selection_filename, NULL );
135 if ( filename ) {
136 fuse_emulation_pause();
137 utils_open_file( filename, settings_current.auto_load, NULL );
138 free( filename );
139 display_refresh_all();
140 fuse_emulation_unpause();
141 }
142
143 g_free( selection_filename );
144 }
145 gtk_drag_finish( drag_context, FALSE, FALSE, timestamp );
146 }
147
148 int
ui_init(int * argc,char *** argv)149 ui_init( int *argc, char ***argv )
150 {
151 GtkWidget *box;
152 GtkAccelGroup *accel_group;
153 GtkSettings *settings;
154
155 #if GTK_CHECK_VERSION( 3, 10, 0 )
156 /* The Wayland output is buggy, see #367 */
157 gdk_set_allowed_backends( "quartz,win32,mir,x11,*" );
158 #endif
159
160 gtk_init(argc,argv);
161
162 #if !GTK_CHECK_VERSION( 3, 0, 0 )
163 gdk_rgb_init();
164 gdk_rgb_set_install( TRUE );
165 gtk_widget_set_default_colormap( gdk_rgb_get_cmap() );
166 gtk_widget_set_default_visual( gdk_rgb_get_visual() );
167 #endif /* #if !GTK_CHECK_VERSION( 3, 0, 0 ) */
168
169 gtkui_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
170
171 #ifdef FUSE_ICON_AVAILABLE
172 gtk_window_set_icon_name( GTK_WINDOW( gtkui_window ), "fuse" );
173 #endif
174
175 settings = gtk_widget_get_settings( GTK_WIDGET( gtkui_window ) );
176 g_object_set( settings, "gtk-menu-bar-accel", "F1", NULL );
177 gtk_window_set_title( GTK_WINDOW(gtkui_window), "Fuse" );
178
179 g_signal_connect(G_OBJECT(gtkui_window), "delete-event",
180 G_CALLBACK(gtkui_delete), NULL);
181 g_signal_connect(G_OBJECT(gtkui_window), "key-press-event",
182 G_CALLBACK(gtkkeyboard_keypress), NULL);
183 gtk_widget_add_events( gtkui_window, GDK_KEY_RELEASE_MASK );
184 g_signal_connect(G_OBJECT(gtkui_window), "key-release-event",
185 G_CALLBACK(gtkkeyboard_keyrelease), NULL);
186
187 /* If we lose the focus, disable all keys */
188 g_signal_connect( G_OBJECT( gtkui_window ), "focus-out-event",
189 G_CALLBACK( gtkui_lose_focus ), NULL );
190 g_signal_connect( G_OBJECT( gtkui_window ), "focus-in-event",
191 G_CALLBACK( gtkui_gain_focus ), NULL );
192
193 gtk_drag_dest_set( GTK_WIDGET( gtkui_window ),
194 GTK_DEST_DEFAULT_ALL,
195 drag_types,
196 ARRAY_SIZE( drag_types ),
197 GDK_ACTION_COPY | GDK_ACTION_PRIVATE | GDK_ACTION_MOVE );
198 /* GDK_ACTION_PRIVATE alone DNW with ROX-Filer,
199 GDK_ACTION_MOVE allow DnD from KDE */
200
201 g_signal_connect( G_OBJECT( gtkui_window ), "drag-data-received",
202 G_CALLBACK( gtkui_drag_data_received ), NULL );
203
204 box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
205 gtk_container_add(GTK_CONTAINER(gtkui_window), box);
206
207 if( gtkui_make_menu( &accel_group, &menu_bar, gtkui_menu_data,
208 gtkui_menu_data_size ) ) {
209 fprintf(stderr,"%s: couldn't make menus %s:%d\n",
210 fuse_progname,__FILE__,__LINE__);
211 return 1;
212 }
213
214 gtk_window_add_accel_group( GTK_WINDOW(gtkui_window), accel_group );
215 gtk_box_pack_start( GTK_BOX(box), menu_bar, FALSE, FALSE, 0 );
216
217 gtkui_drawing_area = gtk_drawing_area_new();
218 if(!gtkui_drawing_area) {
219 fprintf(stderr,"%s: couldn't create drawing area at %s:%d\n",
220 fuse_progname,__FILE__,__LINE__);
221 return 1;
222 }
223
224 /* Set minimum size for drawing area */
225 gtk_widget_set_size_request( gtkui_drawing_area, DISPLAY_ASPECT_WIDTH,
226 DISPLAY_SCREEN_HEIGHT );
227
228 gtkmouse_init();
229
230 gtk_box_pack_start( GTK_BOX(box), gtkui_drawing_area, TRUE, TRUE, 0 );
231
232 /* Create the statusbar */
233 gtkstatusbar_create( GTK_BOX( box ) );
234
235 gtk_widget_show_all( gtkui_window );
236 gtkstatusbar_set_visibility( settings_current.statusbar );
237
238 ui_mouse_present = 1;
239
240 return 0;
241 }
242
243 static void
gtkui_menu_deactivate(GtkMenuShell * menu GCC_UNUSED,gpointer data GCC_UNUSED)244 gtkui_menu_deactivate( GtkMenuShell *menu GCC_UNUSED,
245 gpointer data GCC_UNUSED )
246 {
247 ui_mouse_resume();
248 }
249
250 static gboolean
gtkui_make_menu(GtkAccelGroup ** accel_group,GtkWidget ** menu_bar,GtkActionEntry * menu_data,guint menu_data_size)251 gtkui_make_menu(GtkAccelGroup **accel_group,
252 GtkWidget **menu_bar,
253 GtkActionEntry *menu_data,
254 guint menu_data_size)
255 {
256 *accel_group = NULL;
257 *menu_bar = NULL;
258 GError *error = NULL;
259 char ui_file[ PATH_MAX ];
260
261 ui_manager_menu = gtk_ui_manager_new();
262
263 /* Load actions */
264 GtkActionGroup *menu_action_group = gtk_action_group_new( "MenuActionGroup" );
265 gtk_action_group_add_actions( menu_action_group, menu_data, menu_data_size,
266 NULL );
267 gtk_ui_manager_insert_action_group( ui_manager_menu, menu_action_group, 0 );
268 g_object_unref( menu_action_group );
269
270 /* Load the UI */
271 if( utils_find_file_path( "menu_data.ui", ui_file, UTILS_AUXILIARY_GTK ) ) {
272 fprintf( stderr, "%s: Error getting path for menu_data.ui\n",
273 fuse_progname );
274 return TRUE;
275 }
276
277 guint ui_menu_id = gtk_ui_manager_add_ui_from_file( ui_manager_menu, ui_file,
278 &error );
279 if( error ) {
280 g_error_free( error );
281 return TRUE;
282 }
283 else if( !ui_menu_id ) return TRUE;
284
285 *accel_group = gtk_ui_manager_get_accel_group( ui_manager_menu );
286
287 *menu_bar = gtk_ui_manager_get_widget( ui_manager_menu, "/MainMenu" );
288 g_signal_connect( G_OBJECT( *menu_bar ), "deactivate",
289 G_CALLBACK( gtkui_menu_deactivate ), NULL );
290
291 /* Start various menus in the 'off' state */
292 ui_menu_activate( UI_MENU_ITEM_AY_LOGGING, 0 );
293 ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_RECORDING, 0 );
294 ui_menu_activate( UI_MENU_ITEM_MACHINE_PROFILER, 0 );
295 ui_menu_activate( UI_MENU_ITEM_RECORDING, 0 );
296 ui_menu_activate( UI_MENU_ITEM_RECORDING_ROLLBACK, 0 );
297 ui_menu_activate( UI_MENU_ITEM_TAPE_RECORDING, 0 );
298 #ifdef HAVE_LIB_XML2
299 ui_menu_activate( UI_MENU_ITEM_FILE_SVG_CAPTURE, 0 );
300 #endif
301 return FALSE;
302 }
303
304 int
ui_event(void)305 ui_event(void)
306 {
307 while(gtk_events_pending())
308 gtk_main_iteration();
309 return 0;
310 }
311
312 int
ui_end(void)313 ui_end(void)
314 {
315 /* Don't display the window whilst doing all this! */
316 gtk_widget_hide( gtkui_window );
317
318 g_object_unref( ui_manager_menu );
319
320 return 0;
321 }
322
323 /* Create a dialog box with the given error message */
324 int
ui_error_specific(ui_error_level severity,const char * message)325 ui_error_specific( ui_error_level severity, const char *message )
326 {
327 GtkWidget *dialog, *label, *vbox, *content_area;
328 const gchar *title;
329
330 /* If we don't have a UI yet, we can't output widgets */
331 if( !display_ui_initialised ) return 0;
332
333 /* Set the appropriate title */
334 switch( severity ) {
335 case UI_ERROR_INFO: title = "Fuse - Info"; break;
336 case UI_ERROR_WARNING: title = "Fuse - Warning"; break;
337 case UI_ERROR_ERROR: title = "Fuse - Error"; break;
338 default: title = "Fuse - (Unknown Error Level)"; break;
339 }
340
341 /* Create the dialog box */
342 dialog = gtkstock_dialog_new( title, G_CALLBACK( gtk_widget_destroy ) );
343
344 /* Add the OK button into the lower half */
345 gtkstock_create_close( dialog, NULL, G_CALLBACK (gtk_widget_destroy),
346 FALSE );
347
348 /* Create a label with that message */
349 label = gtk_label_new( message );
350
351 /* Make a new vbox for the top part for saner spacing */
352 vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
353 content_area = gtk_dialog_get_content_area( GTK_DIALOG( dialog ) );
354 gtk_box_pack_start( GTK_BOX( content_area ), vbox, TRUE, TRUE, 0 );
355 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
356
357 /* Put the label in it */
358 gtk_container_add( GTK_CONTAINER( vbox ), label );
359
360 gtk_widget_show_all( dialog );
361
362 return 0;
363 }
364
365 /* The callbacks used by various routines */
366
367 static gboolean
gtkui_lose_focus(GtkWidget * widget GCC_UNUSED,GdkEvent * event GCC_UNUSED,gpointer data GCC_UNUSED)368 gtkui_lose_focus( GtkWidget *widget GCC_UNUSED,
369 GdkEvent *event GCC_UNUSED, gpointer data GCC_UNUSED )
370 {
371 keyboard_release_all();
372 ui_mouse_suspend();
373 return TRUE;
374 }
375
376 static gboolean
gtkui_gain_focus(GtkWidget * widget GCC_UNUSED,GdkEvent * event GCC_UNUSED,gpointer data GCC_UNUSED)377 gtkui_gain_focus( GtkWidget *widget GCC_UNUSED,
378 GdkEvent *event GCC_UNUSED, gpointer data GCC_UNUSED )
379 {
380 ui_mouse_resume();
381 return TRUE;
382 }
383
384 /* Called by the main window on a "delete-event" */
385 static gboolean
gtkui_delete(GtkWidget * widget GCC_UNUSED,GdkEvent * event GCC_UNUSED,gpointer data GCC_UNUSED)386 gtkui_delete( GtkWidget *widget GCC_UNUSED, GdkEvent *event GCC_UNUSED,
387 gpointer data GCC_UNUSED )
388 {
389 menu_file_exit( NULL, NULL );
390 return TRUE;
391 }
392
393 /* Called by the menu when File/Exit selected */
394 void
menu_file_exit(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)395 menu_file_exit( GtkAction *gtk_action GCC_UNUSED, gpointer data GCC_UNUSED )
396 {
397 if( gtkui_confirm( "Exit Fuse?" ) ) {
398
399 if( menu_check_media_changed() ) return;
400
401 fuse_exiting = 1;
402
403 /* Stop the paused state to allow us to exit (occurs from main
404 emulation loop) */
405 if( paused ) menu_machine_pause( NULL, NULL );
406
407 /* Ensure we break out of the main Z80 loop, there could be active
408 breakpoints before the next event */
409 debugger_exit_emulator( NULL );
410 }
411 }
412
413 /* Select a graphics filter from those for which `available' returns
414 true */
415 scaler_type
menu_get_scaler(scaler_available_fn selector)416 menu_get_scaler( scaler_available_fn selector )
417 {
418 GtkWidget *content_area;
419 gtkui_select_info dialog;
420 GSList *button_group = NULL;
421
422 int count;
423 scaler_type scaler;
424
425 /* Store the function which tells us which scalers are currently
426 available */
427 dialog.selector = selector;
428
429 /* No scaler currently selected */
430 dialog.selected = SCALER_NUM;
431
432 /* Some space to store the radio buttons in */
433 dialog.buttons = malloc( SCALER_NUM * sizeof(GtkWidget* ) );
434 if( dialog.buttons == NULL ) {
435 ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ );
436 return SCALER_NUM;
437 }
438
439 count = 0;
440
441 /* Create the necessary widgets */
442 dialog.dialog = gtkstock_dialog_new( "Fuse - Select Scaler", NULL );
443 content_area = gtk_dialog_get_content_area( GTK_DIALOG( dialog.dialog ) );
444
445 for( scaler = 0; scaler < SCALER_NUM; scaler++ ) {
446
447 if( !selector( scaler ) ) continue;
448
449 dialog.buttons[ count ] =
450 gtk_radio_button_new_with_label( button_group, scaler_name( scaler ) );
451 button_group =
452 gtk_radio_button_get_group( GTK_RADIO_BUTTON( dialog.buttons[ count ] ) );
453
454 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( dialog.buttons[ count ] ),
455 current_scaler == scaler );
456
457 gtk_container_add( GTK_CONTAINER( content_area ), dialog.buttons[ count ] );
458
459 count++;
460 }
461
462 /* Create and add the actions buttons to the dialog box */
463 gtkstock_create_ok_cancel( dialog.dialog, NULL,
464 G_CALLBACK( menu_options_filter_done ),
465 (gpointer) &dialog, DEFAULT_DESTROY,
466 DEFAULT_DESTROY );
467
468 gtk_dialog_set_default_response( GTK_DIALOG( dialog.dialog ),
469 GTK_RESPONSE_OK );
470
471 gtk_widget_show_all( dialog.dialog );
472
473 /* Process events until the window is done with */
474 gtk_main();
475
476 return dialog.selected;
477 }
478
479 /* Callback used by the filter selection dialog */
480 static void
menu_options_filter_done(GtkWidget * widget GCC_UNUSED,gpointer user_data)481 menu_options_filter_done( GtkWidget *widget GCC_UNUSED, gpointer user_data )
482 {
483 int i, count;
484 gtkui_select_info *ptr = (gtkui_select_info*)user_data;
485
486 count = 0;
487
488 for( i = 0; i < SCALER_NUM; i++ ) {
489
490 if( !ptr->selector( i ) ) continue;
491
492 if( gtk_toggle_button_get_active(
493 GTK_TOGGLE_BUTTON( ptr->buttons[ count ] )
494 )
495 ) {
496 ptr->selected = i;
497 }
498
499 count++;
500 }
501
502 gtk_widget_destroy( ptr->dialog );
503 gtk_main_quit();
504 }
505
506 static gboolean
gtkui_run_main_loop(gpointer user_data GCC_UNUSED)507 gtkui_run_main_loop( gpointer user_data GCC_UNUSED )
508 {
509 gtk_main();
510 return FALSE;
511 }
512
513 /* Machine/Pause */
514 void
menu_machine_pause(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)515 menu_machine_pause( GtkAction *gtk_action GCC_UNUSED, gpointer data GCC_UNUSED )
516 {
517 int error;
518
519 if( paused ) {
520 paused = 0;
521 ui_statusbar_update( UI_STATUSBAR_ITEM_PAUSED,
522 UI_STATUSBAR_STATE_INACTIVE );
523 timer_estimate_reset();
524 gtk_main_quit();
525 } else {
526
527 /* Stop recording any competition mode RZX file */
528 if( rzx_recording && rzx_competition_mode ) {
529 ui_error( UI_ERROR_INFO, "Stopping competition mode RZX recording" );
530 error = rzx_stop_recording(); if( error ) return;
531 }
532
533 paused = 1;
534 ui_statusbar_update( UI_STATUSBAR_ITEM_PAUSED, UI_STATUSBAR_STATE_ACTIVE );
535
536 /* Create nested main loop outside this callback to allow unpause */
537 g_idle_add( (GSourceFunc)gtkui_run_main_loop, NULL );
538 }
539
540 }
541
542 /* Called by the menu when Machine/Reset selected */
543 void
menu_machine_reset(GtkAction * gtk_action GCC_UNUSED,guint action)544 menu_machine_reset( GtkAction *gtk_action GCC_UNUSED, guint action )
545 {
546 int hard_reset = action;
547 const char *message = "Reset?";
548
549 if( hard_reset )
550 message = "Hard reset?";
551
552 if( !gtkui_confirm( message ) )
553 return;
554
555 /* Stop any ongoing RZX */
556 rzx_stop_recording();
557 rzx_stop_playback( 1 );
558
559 if( machine_reset( hard_reset ) ) {
560 ui_error( UI_ERROR_ERROR, "couldn't reset machine: giving up!" );
561
562 /* FIXME: abort() seems a bit extreme here, but it'll do for now */
563 fuse_abort();
564 }
565 }
566
567 /* Called by the menu when Machine/Select selected */
568 void
menu_machine_select(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)569 menu_machine_select( GtkAction *gtk_action GCC_UNUSED,
570 gpointer data GCC_UNUSED )
571 {
572 GtkWidget *content_area;
573 gtkui_select_info dialog;
574
575 int i;
576
577 /* Some space to store the radio buttons in */
578 dialog.buttons = malloc( machine_count * sizeof(GtkWidget* ) );
579 if( dialog.buttons == NULL ) {
580 ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ );
581 return;
582 }
583
584 /* Stop emulation */
585 fuse_emulation_pause();
586
587 /* Create the necessary widgets */
588 dialog.dialog = gtkstock_dialog_new( "Fuse - Select Machine", NULL );
589 content_area = gtk_dialog_get_content_area( GTK_DIALOG( dialog.dialog ) );
590
591 dialog.buttons[0] =
592 gtk_radio_button_new_with_label(
593 NULL, libspectrum_machine_name( machine_types[0]->machine )
594 );
595
596 for( i=1; i<machine_count; i++ ) {
597 dialog.buttons[i] =
598 gtk_radio_button_new_with_label(
599 gtk_radio_button_get_group( GTK_RADIO_BUTTON( dialog.buttons[i-1] ) ),
600 libspectrum_machine_name( machine_types[i]->machine )
601 );
602 }
603
604 for( i=0; i<machine_count; i++ ) {
605 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( dialog.buttons[i] ),
606 machine_current == machine_types[i] );
607 gtk_container_add( GTK_CONTAINER( content_area ), dialog.buttons[i] );
608 }
609
610 /* Create and add the actions buttons to the dialog box */
611 gtkstock_create_ok_cancel( dialog.dialog, NULL,
612 G_CALLBACK( menu_machine_select_done ),
613 (gpointer) &dialog, DEFAULT_DESTROY,
614 DEFAULT_DESTROY );
615
616 gtk_dialog_set_default_response( GTK_DIALOG( dialog.dialog ),
617 GTK_RESPONSE_OK );
618
619 gtk_widget_show_all( dialog.dialog );
620
621 /* Process events until the window is done with */
622 gtk_main();
623
624 /* And then carry on with emulation again */
625 fuse_emulation_unpause();
626 }
627
628 /* Callback used by the machine selection dialog */
629 static void
menu_machine_select_done(GtkWidget * widget GCC_UNUSED,gpointer user_data)630 menu_machine_select_done( GtkWidget *widget GCC_UNUSED, gpointer user_data )
631 {
632 int i;
633 gtkui_select_info *ptr = user_data;
634
635 for( i=0; i<machine_count; i++ ) {
636 if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(ptr->buttons[i]) ) &&
637 machine_current != machine_types[i]
638 )
639 {
640 machine_select( machine_types[i]->machine );
641 }
642 }
643
644 gtk_widget_destroy( ptr->dialog );
645 gtk_main_quit();
646 }
647
648 void
menu_machine_debugger(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)649 menu_machine_debugger( GtkAction *gtk_action GCC_UNUSED,
650 gpointer data GCC_UNUSED )
651 {
652 debugger_mode = DEBUGGER_MODE_HALTED;
653 if( paused ) ui_debugger_activate();
654 }
655
656 /* Called on machine selection */
657 int
ui_widgets_reset(void)658 ui_widgets_reset( void )
659 {
660 gtkui_pokefinder_clear();
661 return 0;
662 }
663
664 void
menu_help_keyboard(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)665 menu_help_keyboard( GtkAction *gtk_action GCC_UNUSED, gpointer data GCC_UNUSED )
666 {
667 gtkui_picture( "keyboard.png", 0 );
668 }
669
670 void
menu_help_about(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)671 menu_help_about( GtkAction *gtk_action GCC_UNUSED, gpointer data GCC_UNUSED )
672 {
673 gtk_show_about_dialog( GTK_WINDOW( gtkui_window ),
674 "program-name", "Fuse",
675 "comments", "The Free Unix Spectrum Emulator",
676 "copyright", FUSE_COPYRIGHT,
677 #ifdef FUSE_ICON_AVAILABLE
678 "logo-icon-name", "fuse",
679 #else
680 "logo-icon-name", NULL,
681 #endif
682 "version", VERSION,
683 "website", PACKAGE_URL,
684 NULL );
685 }
686
687 /* Generic `tidy-up' callback */
688 void
gtkui_destroy_widget_and_quit(GtkWidget * widget,gpointer data GCC_UNUSED)689 gtkui_destroy_widget_and_quit( GtkWidget *widget, gpointer data GCC_UNUSED )
690 {
691 gtk_widget_destroy( widget );
692 gtk_main_quit();
693 }
694
695 /* Functions to activate and deactivate certain menu items */
696
697 int
ui_menu_item_set_active(const char * path,int active)698 ui_menu_item_set_active( const char *path, int active )
699 {
700 GtkWidget *menu_item;
701
702 /* Translate UI-indepentment path to GTK UI path */
703 gchar *full_path = g_strdup_printf ("/MainMenu%s", path );
704
705 menu_item = gtk_ui_manager_get_widget( ui_manager_menu, full_path );
706 g_free( full_path );
707
708 if( !menu_item ) {
709 ui_error( UI_ERROR_ERROR, "couldn't get menu item '%s' from menu_factory",
710 path );
711 return 1;
712 }
713 gtk_widget_set_sensitive( menu_item, active );
714
715 return 0;
716 }
717
718 static void
confirm_joystick_done(GtkWidget * widget GCC_UNUSED,gpointer user_data)719 confirm_joystick_done( GtkWidget *widget GCC_UNUSED, gpointer user_data )
720 {
721 int i;
722 gtkui_select_info *ptr = user_data;
723
724 for( i = 0; i < JOYSTICK_CONN_COUNT; i++ ) {
725
726 GtkToggleButton *button = GTK_TOGGLE_BUTTON( ptr->buttons[ i ] );
727
728 if( gtk_toggle_button_get_active( button ) ) {
729 ptr->joystick = i;
730 break;
731 }
732 }
733
734 gtk_widget_destroy( ptr->dialog );
735 gtk_main_quit();
736 }
737
738 ui_confirm_joystick_t
ui_confirm_joystick(libspectrum_joystick libspectrum_type,int inputs GCC_UNUSED)739 ui_confirm_joystick( libspectrum_joystick libspectrum_type,
740 int inputs GCC_UNUSED )
741 {
742 GtkWidget *content_area;
743 gtkui_select_info dialog;
744 char title[ 80 ];
745 int i;
746 GSList *group = NULL;
747
748 if( !settings_current.joy_prompt ) return UI_CONFIRM_JOYSTICK_NONE;
749
750 /* Some space to store the radio buttons in */
751 dialog.buttons =
752 malloc( JOYSTICK_CONN_COUNT * sizeof( *dialog.buttons ) );
753 if( !dialog.buttons ) {
754 ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ );
755 return UI_CONFIRM_JOYSTICK_NONE;
756 }
757
758 /* Stop emulation */
759 fuse_emulation_pause();
760
761 /* Create the necessary widgets */
762 snprintf( title, sizeof( title ), "Fuse - Configure %s Joystick",
763 libspectrum_joystick_name( libspectrum_type ) );
764 dialog.dialog = gtkstock_dialog_new( title, NULL );
765 content_area = gtk_dialog_get_content_area( GTK_DIALOG( dialog.dialog ) );
766
767 for( i = 0; i < JOYSTICK_CONN_COUNT; i++ ) {
768
769 GtkWidget **button = &( dialog.buttons[ i ] );
770
771 *button =
772 gtk_radio_button_new_with_label( group, joystick_connection[ i ] );
773 group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( *button ) );
774
775 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( *button ), i == 0 );
776 gtk_container_add( GTK_CONTAINER( content_area ), *button );
777 }
778
779 /* Create and add the actions buttons to the dialog box */
780 gtkstock_create_ok_cancel( dialog.dialog, NULL,
781 G_CALLBACK( confirm_joystick_done ),
782 (gpointer) &dialog, DEFAULT_DESTROY,
783 DEFAULT_DESTROY );
784
785 gtk_dialog_set_default_response( GTK_DIALOG( dialog.dialog ),
786 GTK_RESPONSE_OK );
787
788 gtk_widget_show_all( dialog.dialog );
789
790 /* Process events until the window is done with */
791 dialog.joystick = UI_CONFIRM_JOYSTICK_NONE;
792 gtk_main();
793
794 /* And then carry on with emulation again */
795 fuse_emulation_unpause();
796
797 return dialog.joystick;
798 }
799
800 /*
801 * Font code
802 */
803
804 int
gtkui_get_monospaced_font(PangoFontDescription ** font)805 gtkui_get_monospaced_font( PangoFontDescription **font )
806 {
807 *font = pango_font_description_from_string( "Monospace 10" );
808 if( !(*font) ) {
809 ui_error( UI_ERROR_ERROR, "couldn't find a monospaced font" );
810 return 1;
811 }
812
813 return 0;
814 }
815
816 void
gtkui_free_font(PangoFontDescription * font)817 gtkui_free_font( PangoFontDescription *font )
818 {
819 pango_font_description_free( font );
820 }
821
822 void
gtkui_list_set_cursor(GtkTreeView * list,int row)823 gtkui_list_set_cursor( GtkTreeView *list, int row )
824 {
825 GtkTreePath *path;
826
827 if( row >= 0 ) {
828 path = gtk_tree_path_new_from_indices( row, -1 );
829 gtk_tree_view_set_cursor( list, path, NULL, FALSE );
830 gtk_tree_path_free( path );
831 }
832 }
833
834 int
gtkui_list_get_cursor(GtkTreeView * list)835 gtkui_list_get_cursor( GtkTreeView *list )
836 {
837 GtkTreePath *path;
838 GtkTreeViewColumn *focus_column;
839 int *indices;
840 int row = -1;
841
842 /* Get selected row */
843 gtk_tree_view_get_cursor( list, &path, &focus_column );
844 if( path ) {
845 indices = gtk_tree_path_get_indices( path );
846 if( indices ) row = indices[0];
847 gtk_tree_path_free( path );
848 }
849
850 return row;
851 }
852
853 static gboolean
key_press(GtkTreeView * list,GdkEventKey * event,gpointer user_data)854 key_press( GtkTreeView *list, GdkEventKey *event, gpointer user_data )
855 {
856 GtkAdjustment *adjustment = user_data;
857 gdouble base, oldbase, base_limit;
858 gdouble page_size, step_increment;
859 int cursor_row;
860 int num_rows;
861
862 base = oldbase = gtk_adjustment_get_value( adjustment );
863 page_size = gtk_adjustment_get_page_size( adjustment );
864 step_increment = gtk_adjustment_get_step_increment( adjustment );
865 num_rows = ( page_size + 1 ) / step_increment;
866
867 /* Get selected row */
868 cursor_row = gtkui_list_get_cursor( list );
869
870 switch( event->keyval )
871 {
872 case GDK_KEY_Up:
873 if( cursor_row == 0 )
874 base -= step_increment;
875 break;
876
877 case GDK_KEY_Down:
878 if( cursor_row == num_rows - 1 )
879 base += step_increment;
880 break;
881
882 case GDK_KEY_Page_Up:
883 base -= gtk_adjustment_get_page_increment( adjustment );
884 break;
885
886 case GDK_KEY_Page_Down:
887 base += gtk_adjustment_get_page_increment( adjustment );
888 break;
889
890 case GDK_KEY_Home:
891 cursor_row = 0;
892 base = gtk_adjustment_get_lower( adjustment );
893 break;
894
895 case GDK_KEY_End:
896 cursor_row = num_rows - 1;
897 base = gtk_adjustment_get_upper( adjustment ) - page_size;
898 break;
899
900 default:
901 return FALSE;
902 }
903
904 if( base < 0 ) {
905 base = 0;
906 } else {
907 base_limit = gtk_adjustment_get_upper( adjustment ) - page_size;
908 if( base > base_limit ) base = base_limit;
909 }
910
911 if( base != oldbase ) {
912 gtk_adjustment_set_value( adjustment, base );
913
914 /* Mark selected row */
915 gtkui_list_set_cursor( list, cursor_row );
916 return TRUE;
917 }
918
919 return FALSE;
920 }
921
922 static gboolean
wheel_scroll_event(GtkTreeView * list,GdkEvent * event,gpointer user_data)923 wheel_scroll_event( GtkTreeView *list, GdkEvent *event, gpointer user_data )
924 {
925 GtkAdjustment *adjustment = user_data;
926 gdouble base, oldbase, base_limit;
927 int cursor_row;
928
929 base = oldbase = gtk_adjustment_get_value( adjustment );
930
931 switch( event->scroll.direction )
932 {
933 case GDK_SCROLL_UP:
934 base -= gtk_adjustment_get_page_increment( adjustment ) / 2;
935 break;
936 case GDK_SCROLL_DOWN:
937 base += gtk_adjustment_get_page_increment( adjustment ) / 2;
938 break;
939
940 #if GTK_CHECK_VERSION( 3, 4, 0 )
941 case GDK_SCROLL_SMOOTH:
942 {
943 static gdouble total_dy = 0;
944 gdouble dx, dy, page_size;
945 int delta;
946
947 if( gdk_event_get_scroll_deltas( event, &dx, &dy ) ) {
948 total_dy += dy;
949 page_size = gtk_adjustment_get_page_size( adjustment );
950 delta = total_dy * pow( page_size, 2.0 / 3.0 );
951
952 /* Is movement significative? */
953 if( delta ) {
954 base += delta;
955 total_dy = 0;
956 }
957 }
958 break;
959 }
960 #endif
961
962 default:
963 return FALSE;
964 }
965
966 if( base < 0 ) {
967 base = 0;
968 } else {
969 base_limit = gtk_adjustment_get_upper( adjustment ) -
970 gtk_adjustment_get_page_size( adjustment );
971 if( base > base_limit ) base = base_limit;
972 }
973
974 if( base != oldbase ) {
975 cursor_row = gtkui_list_get_cursor( list );
976 gtk_adjustment_set_value( adjustment, base );
977 gtkui_list_set_cursor( list, cursor_row );
978 }
979
980 return TRUE;
981 }
982
983 void
gtkui_scroll_connect(GtkTreeView * list,GtkAdjustment * adj)984 gtkui_scroll_connect( GtkTreeView *list, GtkAdjustment *adj )
985 {
986 g_signal_connect( list, "key-press-event",
987 G_CALLBACK( key_press ), adj );
988 g_signal_connect( list, "scroll-event",
989 G_CALLBACK( wheel_scroll_event ), adj );
990 }
991
992 int
gtkui_menubar_get_height(void)993 gtkui_menubar_get_height( void )
994 {
995 GtkAllocation alloc;
996
997 gtk_widget_get_allocation( menu_bar, &alloc );
998
999 return alloc.height;
1000 }
1001