1 /** \file   uimenu.c
2  * \brief   Native GTK3 menu handling
3  *
4  * \author  Bas Wassink <b.wassink@ziggo.nl>
5  * \author  Marcus Sutton <loggedoubt@gmail.com>
6  */
7 
8 /*
9  * This file is part of VICE, the Versatile Commodore Emulator.
10  * See README for copyright notice.
11  *
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
25  *  02111-1307  USA.
26  */
27 
28 #include "vice.h"
29 
30 #include <stdio.h>
31 #include <string.h>
32 #include <gtk/gtk.h>
33 
34 #include "debug_gtk3.h"
35 
36 #include "kbd.h"
37 #include "lib.h"
38 #include "machine.h"
39 #include "resources.h"
40 #include "uiapi.h"
41 #include "uiabout.h"
42 #include "uistatusbar.h"
43 #include "util.h"
44 
45 #include "uimenu.h"
46 
47 
48 /** \brief  Menu accelerator object
49  */
50 typedef struct ui_accel_data_s {
51     GtkWidget *widget;      /**< widget connected to the accelerator */
52     ui_menu_item_t *item;   /**< menu item connected to the accelerator */
53 } ui_accel_data_t;
54 
55 
56 /** \brief  Reference to the accelerator group
57  */
58 static GtkAccelGroup *accel_group = NULL;
59 
60 
61 /** \brief  Create an empty submenu and add it to a menu bar
62  *
63  * \param[in]       bar     the menu bar to add the submenu to
64  * \param[in]       label   label of the submenu to create
65  *
66  * \return  a reference to the new submenu
67 */
ui_menu_submenu_create(GtkWidget * bar,const char * label)68 GtkWidget *ui_menu_submenu_create(GtkWidget *bar, const char *label)
69 {
70     GtkWidget *submenu_item;
71     GtkWidget *new_submenu;
72 
73     submenu_item = gtk_menu_item_new_with_label(label);
74     new_submenu = gtk_menu_new();
75     gtk_menu_item_set_submenu(GTK_MENU_ITEM(submenu_item), new_submenu);
76     gtk_menu_shell_append(GTK_MENU_SHELL(bar), submenu_item);
77 
78     return new_submenu;
79 }
80 
81 /** \brief  Constructor for accelerator data */
ui_accel_data_new(GtkWidget * widget,ui_menu_item_t * item)82 static ui_accel_data_t *ui_accel_data_new(GtkWidget *widget, ui_menu_item_t *item)
83 {
84     ui_accel_data_t *accel_data = lib_malloc(sizeof(ui_accel_data_t));
85     accel_data->widget = widget;
86     accel_data->item = item;
87     return accel_data;
88 }
89 
90 /** \brief  Destructor for accelerator data
91  *
92  * FIXME:   this doesn't get triggered
93  */
ui_accel_data_delete(gpointer data,GClosure * closure)94 static void ui_accel_data_delete(gpointer data, GClosure *closure)
95 {
96     debug_gtk3("Freeing accelerator data\n");
97     lib_free(data);
98 }
99 
100 /** \brief  Callback that forwards accelerator codes.
101  */
handle_accelerator(GtkAccelGroup * accel_grp,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer user_data)102 static void handle_accelerator(GtkAccelGroup *accel_grp,
103                                GObject *acceleratable,
104                                guint keyval,
105                                GdkModifierType modifier,
106                                gpointer user_data)
107 {
108     ui_accel_data_t *accel_data = (ui_accel_data_t *)user_data;
109     accel_data->item->callback(accel_data->widget, accel_data->item->data);
110 }
111 
112 /** \brief  Add menu \a items to \a menu
113  *
114  * \param[in,out]   menu    Gtk menu
115  * \param[in]       items   menu items to add to \a menu
116  *
117  * \return  \a menu
118  */
ui_menu_add(GtkWidget * menu,ui_menu_item_t * items)119 GtkWidget *ui_menu_add(GtkWidget *menu, ui_menu_item_t *items)
120 {
121     size_t i = 0;
122     while (items[i].label != NULL || items[i].type >= 0) {
123         GtkWidget *item = NULL;
124         GtkWidget *submenu;
125 
126         switch (items[i].type) {
127             case UI_MENU_TYPE_ITEM_ACTION:  /* fall through */
128                 /* normal callback item */
129                 /* debug_gtk3("adding menu item '%s'\n", items[i].label); */
130                 item = gtk_menu_item_new_with_mnemonic(items[i].label);
131                 if (items[i].callback != NULL) {
132                     g_signal_connect(
133                             item,
134                             "activate",
135                             G_CALLBACK(items[i].callback),
136                             (gpointer)(items[i].data));
137                 } else {
138                     /* no callback: 'grey-out'/'ghost' the item */
139                     gtk_widget_set_sensitive(item, FALSE);
140                 }
141                 break;
142             case UI_MENU_TYPE_ITEM_CHECK:
143                 /* check mark item */
144                 item = gtk_check_menu_item_new_with_mnemonic(items[i].label);
145                 if (items[i].callback != NULL) {
146                    /* use `data` as the resource to determine the state of
147                      * the checkmark
148                      */
149                     if (items[i].data != NULL) {
150                         int state;
151                         resources_get_int((const char *)items[i].data, & state);
152                         gtk_check_menu_item_set_active(
153                                 GTK_CHECK_MENU_ITEM(item), (gboolean)state);
154                     }
155                     /* connect signal handler AFTER setting the state, otherwise
156                      * the callback gets triggered, leading to odd results */
157                     g_signal_connect(
158                             item,
159                             "activate",
160                             G_CALLBACK(items[i].callback),
161                             items[i].data);
162                 } else {
163                     /* grey out */
164                     gtk_widget_set_sensitive(item, FALSE);
165                 }
166                 break;
167 
168             case UI_MENU_TYPE_SEPARATOR:
169                 /* add a separator */
170                 item = gtk_separator_menu_item_new();
171                 break;
172 
173             case UI_MENU_TYPE_SUBMENU:
174                 /* add a submenu */
175                 submenu = gtk_menu_new();
176                 item = gtk_menu_item_new_with_mnemonic(items[i].label);
177                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
178                 ui_menu_add(submenu, (ui_menu_item_t *)items[i].data);
179                 break;
180 
181             default:
182                 item = NULL;
183                 break;
184         }
185         if (item != NULL) {
186 
187             if (items[i].keysym != 0 && items[i].callback != NULL) {
188                 GClosure *accel_closure;
189 #if 0
190                 debug_gtk3("adding accelerator %d to item %s'\n",
191                         items[i].keysym, items[i].label);
192 #endif
193                 /* Normally you would use gtk_widget_add_accelerator
194                  * here, but that will disable the accelerators if the
195                  * menu is hidden, which can be configured to happen
196                  * while in fullscreen. We instead create the closure
197                  * by hand, add it to the GtkAccelGroup, and update
198                  * the accelerator information. */
199                 accel_closure = g_cclosure_new(G_CALLBACK(handle_accelerator),
200                                                ui_accel_data_new(item, &items[i]),
201                                                ui_accel_data_delete);
202                 gtk_accel_group_connect(accel_group, items[i].keysym, items[i].modifier, GTK_ACCEL_MASK, accel_closure);
203                 gtk_accel_label_set_accel(GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(item))), items[i].keysym, items[i].modifier);
204             }
205 
206             gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
207         }
208         i++;
209     }
210     return menu;
211 }
212 
213 
214 /** \brief  Create accelerator group and add it to \a window
215  *
216  * \param[in]       window  top level window
217  */
ui_menu_init_accelerators(GtkWidget * window)218 void ui_menu_init_accelerators(GtkWidget *window)
219 {
220     accel_group = gtk_accel_group_new();
221     gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
222 }
223