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