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