1 /*
2 * This file is part of Amtk - Actions, Menus and Toolbars Kit
3 *
4 * Copyright 2016, 2017 - Sébastien Wilmet <swilmet@gnome.org>
5 *
6 * Amtk is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU Lesser General Public License as published by the
8 * Free Software Foundation; either version 2.1 of the License, or (at your
9 * option) any later version.
10 *
11 * Amtk is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 * License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "amtk-menu-shell.h"
21
22 /**
23 * SECTION:amtk-menu-shell
24 * @Short_description: An extension of #GtkMenuShell
25 * @Title: AmtkMenuShell
26 *
27 * #AmtkMenuShell extends the #GtkMenuShell abstract class with the
28 * #AmtkMenuShell::menu-item-selected and #AmtkMenuShell::menu-item-deselected
29 * convenience signals.
30 *
31 * One possible use-case is to push/pop longer descriptions of menu items to a
32 * #GtkStatusbar, exactly like
33 * amtk_application_window_connect_menu_to_statusbar() does.
34 */
35
36 struct _AmtkMenuShellPrivate
37 {
38 GtkMenuShell *gtk_menu_shell;
39 };
40
41 enum
42 {
43 PROP_0,
44 PROP_MENU_SHELL,
45 N_PROPERTIES
46 };
47
48 enum
49 {
50 SIGNAL_MENU_ITEM_SELECTED,
51 SIGNAL_MENU_ITEM_DESELECTED,
52 N_SIGNALS
53 };
54
55 #define AMTK_MENU_SHELL_KEY "amtk-menu-shell-key"
56
57 static GParamSpec *properties[N_PROPERTIES];
58 static guint signals[N_SIGNALS];
59
60 G_DEFINE_TYPE_WITH_PRIVATE (AmtkMenuShell, amtk_menu_shell, G_TYPE_OBJECT)
61
62 /* Prototypes */
63 static void connect_menu_shell (AmtkMenuShell *amtk_menu_shell,
64 GtkMenuShell *gtk_menu_shell);
65
66 static void disconnect_menu_shell (AmtkMenuShell *amtk_menu_shell,
67 GtkMenuShell *gtk_menu_shell);
68
69 static void
menu_item_select_cb(GtkMenuItem * menu_item,gpointer user_data)70 menu_item_select_cb (GtkMenuItem *menu_item,
71 gpointer user_data)
72 {
73 AmtkMenuShell *amtk_menu_shell = AMTK_MENU_SHELL (user_data);
74 GtkWidget *submenu;
75
76 submenu = gtk_menu_item_get_submenu (menu_item);
77
78 if (GTK_IS_MENU_SHELL (submenu))
79 {
80 connect_menu_shell (amtk_menu_shell, GTK_MENU_SHELL (submenu));
81 }
82
83 g_signal_emit (amtk_menu_shell,
84 signals[SIGNAL_MENU_ITEM_SELECTED], 0,
85 menu_item);
86 }
87
88 static void
menu_item_deselect_cb(GtkMenuItem * menu_item,gpointer user_data)89 menu_item_deselect_cb (GtkMenuItem *menu_item,
90 gpointer user_data)
91 {
92 AmtkMenuShell *amtk_menu_shell = AMTK_MENU_SHELL (user_data);
93 GtkWidget *submenu;
94
95 submenu = gtk_menu_item_get_submenu (menu_item);
96
97 if (GTK_IS_MENU_SHELL (submenu))
98 {
99 disconnect_menu_shell (amtk_menu_shell, GTK_MENU_SHELL (submenu));
100 }
101
102 g_signal_emit (amtk_menu_shell,
103 signals[SIGNAL_MENU_ITEM_DESELECTED], 0,
104 menu_item);
105 }
106
107 static void
connect_menu_item(AmtkMenuShell * amtk_menu_shell,GtkMenuItem * menu_item)108 connect_menu_item (AmtkMenuShell *amtk_menu_shell,
109 GtkMenuItem *menu_item)
110 {
111 g_signal_connect_object (menu_item,
112 "select",
113 G_CALLBACK (menu_item_select_cb),
114 amtk_menu_shell,
115 0);
116
117 g_signal_connect_object (menu_item,
118 "deselect",
119 G_CALLBACK (menu_item_deselect_cb),
120 amtk_menu_shell,
121 0);
122 }
123
124 static void
disconnect_menu_item(AmtkMenuShell * amtk_menu_shell,GtkMenuItem * menu_item)125 disconnect_menu_item (AmtkMenuShell *amtk_menu_shell,
126 GtkMenuItem *menu_item)
127 {
128 g_signal_handlers_disconnect_by_func (menu_item,
129 menu_item_select_cb,
130 amtk_menu_shell);
131
132 g_signal_handlers_disconnect_by_func (menu_item,
133 menu_item_deselect_cb,
134 amtk_menu_shell);
135 }
136
137 static void
insert_cb(GtkMenuShell * gtk_menu_shell,GtkWidget * child,gint position,gpointer user_data)138 insert_cb (GtkMenuShell *gtk_menu_shell,
139 GtkWidget *child,
140 gint position,
141 gpointer user_data)
142 {
143 AmtkMenuShell *amtk_menu_shell = AMTK_MENU_SHELL (user_data);
144
145 if (GTK_IS_MENU_ITEM (child))
146 {
147 connect_menu_item (amtk_menu_shell, GTK_MENU_ITEM (child));
148 }
149 }
150
151 static void
remove_cb(GtkContainer * container,GtkWidget * child,gpointer user_data)152 remove_cb (GtkContainer *container,
153 GtkWidget *child,
154 gpointer user_data)
155 {
156 AmtkMenuShell *amtk_menu_shell = AMTK_MENU_SHELL (user_data);
157
158 if (GTK_IS_MENU_ITEM (child))
159 {
160 disconnect_menu_item (amtk_menu_shell, GTK_MENU_ITEM (child));
161 }
162 }
163
164 static void
connect_menu_shell(AmtkMenuShell * amtk_menu_shell,GtkMenuShell * gtk_menu_shell)165 connect_menu_shell (AmtkMenuShell *amtk_menu_shell,
166 GtkMenuShell *gtk_menu_shell)
167 {
168 GList *children;
169 GList *l;
170
171 children = gtk_container_get_children (GTK_CONTAINER (gtk_menu_shell));
172
173 for (l = children; l != NULL; l = l->next)
174 {
175 GtkMenuItem *menu_item = l->data;
176
177 if (GTK_IS_MENU_ITEM (menu_item))
178 {
179 connect_menu_item (amtk_menu_shell, menu_item);
180 }
181 }
182
183 g_list_free (children);
184
185 g_signal_connect_object (gtk_menu_shell,
186 "insert",
187 G_CALLBACK (insert_cb),
188 amtk_menu_shell,
189 0);
190
191 g_signal_connect_object (gtk_menu_shell,
192 "remove",
193 G_CALLBACK (remove_cb),
194 amtk_menu_shell,
195 0);
196 }
197
198 static void
disconnect_menu_shell(AmtkMenuShell * amtk_menu_shell,GtkMenuShell * gtk_menu_shell)199 disconnect_menu_shell (AmtkMenuShell *amtk_menu_shell,
200 GtkMenuShell *gtk_menu_shell)
201 {
202 GList *children;
203 GList *l;
204
205 children = gtk_container_get_children (GTK_CONTAINER (gtk_menu_shell));
206
207 for (l = children; l != NULL; l = l->next)
208 {
209 GtkMenuItem *menu_item = l->data;
210
211 if (GTK_IS_MENU_ITEM (menu_item))
212 {
213 disconnect_menu_item (amtk_menu_shell, menu_item);
214 }
215 }
216
217 g_list_free (children);
218
219 g_signal_handlers_disconnect_by_func (gtk_menu_shell,
220 insert_cb,
221 amtk_menu_shell);
222
223 g_signal_handlers_disconnect_by_func (gtk_menu_shell,
224 remove_cb,
225 amtk_menu_shell);
226 }
227
228 static void
set_menu_shell(AmtkMenuShell * amtk_menu_shell,GtkMenuShell * gtk_menu_shell)229 set_menu_shell (AmtkMenuShell *amtk_menu_shell,
230 GtkMenuShell *gtk_menu_shell)
231 {
232 g_assert (amtk_menu_shell->priv->gtk_menu_shell == NULL);
233 g_return_if_fail (GTK_IS_MENU_SHELL (gtk_menu_shell));
234
235 amtk_menu_shell->priv->gtk_menu_shell = gtk_menu_shell;
236 connect_menu_shell (amtk_menu_shell, gtk_menu_shell);
237 }
238
239 static void
amtk_menu_shell_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)240 amtk_menu_shell_get_property (GObject *object,
241 guint prop_id,
242 GValue *value,
243 GParamSpec *pspec)
244 {
245 AmtkMenuShell *amtk_menu_shell = AMTK_MENU_SHELL (object);
246
247 switch (prop_id)
248 {
249 case PROP_MENU_SHELL:
250 g_value_set_object (value, amtk_menu_shell_get_menu_shell (amtk_menu_shell));
251 break;
252
253 default:
254 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
255 break;
256 }
257 }
258
259 static void
amtk_menu_shell_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)260 amtk_menu_shell_set_property (GObject *object,
261 guint prop_id,
262 const GValue *value,
263 GParamSpec *pspec)
264 {
265 AmtkMenuShell *amtk_menu_shell = AMTK_MENU_SHELL (object);
266
267 switch (prop_id)
268 {
269 case PROP_MENU_SHELL:
270 set_menu_shell (amtk_menu_shell, g_value_get_object (value));
271 break;
272
273 default:
274 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
275 break;
276 }
277 }
278
279 static void
amtk_menu_shell_dispose(GObject * object)280 amtk_menu_shell_dispose (GObject *object)
281 {
282 AmtkMenuShell *amtk_menu_shell = AMTK_MENU_SHELL (object);
283
284 amtk_menu_shell->priv->gtk_menu_shell = NULL;
285
286 G_OBJECT_CLASS (amtk_menu_shell_parent_class)->dispose (object);
287 }
288
289 static void
amtk_menu_shell_class_init(AmtkMenuShellClass * klass)290 amtk_menu_shell_class_init (AmtkMenuShellClass *klass)
291 {
292 GObjectClass *object_class = G_OBJECT_CLASS (klass);
293
294 object_class->get_property = amtk_menu_shell_get_property;
295 object_class->set_property = amtk_menu_shell_set_property;
296 object_class->dispose = amtk_menu_shell_dispose;
297
298 /**
299 * AmtkMenuShell:menu-shell:
300 *
301 * The #GtkMenuShell.
302 *
303 * Since: 2.0
304 */
305 properties[PROP_MENU_SHELL] =
306 g_param_spec_object ("menu-shell",
307 "GtkMenuShell",
308 "",
309 GTK_TYPE_MENU_SHELL,
310 G_PARAM_READWRITE |
311 G_PARAM_CONSTRUCT_ONLY |
312 G_PARAM_STATIC_STRINGS);
313
314 g_object_class_install_properties (object_class, N_PROPERTIES, properties);
315
316 /**
317 * AmtkMenuShell::menu-item-selected:
318 * @amtk_menu_shell: the #AmtkMenuShell emitting the signal.
319 * @menu_item: the #GtkMenuItem that has been selected.
320 *
321 * The ::menu-item-selected signal is emitted when the
322 * #GtkMenuItem::select signal is emitted on a #GtkMenuItem belonging
323 * (directly or indirectly through submenus) to @amtk_menu_shell.
324 *
325 * Since: 2.0
326 */
327 signals[SIGNAL_MENU_ITEM_SELECTED] =
328 g_signal_new ("menu-item-selected",
329 G_TYPE_FROM_CLASS (klass),
330 G_SIGNAL_RUN_FIRST,
331 G_STRUCT_OFFSET (AmtkMenuShellClass, menu_item_selected),
332 NULL, NULL, NULL,
333 G_TYPE_NONE,
334 1, GTK_TYPE_MENU_ITEM);
335
336 /**
337 * AmtkMenuShell::menu-item-deselected:
338 * @amtk_menu_shell: the #AmtkMenuShell emitting the signal.
339 * @menu_item: the #GtkMenuItem that has been deselected.
340 *
341 * The ::menu-item-deselected signal is emitted when the
342 * #GtkMenuItem::deselect signal is emitted on a #GtkMenuItem belonging
343 * (directly or indirectly through submenus) to @amtk_menu_shell.
344 *
345 * Since: 2.0
346 */
347 signals[SIGNAL_MENU_ITEM_DESELECTED] =
348 g_signal_new ("menu-item-deselected",
349 G_TYPE_FROM_CLASS (klass),
350 G_SIGNAL_RUN_FIRST,
351 G_STRUCT_OFFSET (AmtkMenuShellClass, menu_item_deselected),
352 NULL, NULL, NULL,
353 G_TYPE_NONE,
354 1, GTK_TYPE_MENU_ITEM);
355 }
356
357 static void
amtk_menu_shell_init(AmtkMenuShell * amtk_menu_shell)358 amtk_menu_shell_init (AmtkMenuShell *amtk_menu_shell)
359 {
360 amtk_menu_shell->priv = amtk_menu_shell_get_instance_private (amtk_menu_shell);
361 }
362
363 /**
364 * amtk_menu_shell_get_from_gtk_menu_shell:
365 * @gtk_menu_shell: a #GtkMenuShell.
366 *
367 * Returns the #AmtkMenuShell of @gtk_menu_shell. The returned object is
368 * guaranteed to be the same for the lifetime of @gtk_menu_shell.
369 *
370 * Returns: (transfer none): the #AmtkMenuShell of @gtk_menu_shell.
371 * Since: 2.0
372 */
373 AmtkMenuShell *
amtk_menu_shell_get_from_gtk_menu_shell(GtkMenuShell * gtk_menu_shell)374 amtk_menu_shell_get_from_gtk_menu_shell (GtkMenuShell *gtk_menu_shell)
375 {
376 AmtkMenuShell *amtk_menu_shell;
377
378 g_return_val_if_fail (GTK_IS_MENU_SHELL (gtk_menu_shell), NULL);
379
380 amtk_menu_shell = g_object_get_data (G_OBJECT (gtk_menu_shell), AMTK_MENU_SHELL_KEY);
381
382 if (amtk_menu_shell == NULL)
383 {
384 amtk_menu_shell = g_object_new (AMTK_TYPE_MENU_SHELL,
385 "menu-shell", gtk_menu_shell,
386 NULL);
387
388 g_object_set_data_full (G_OBJECT (gtk_menu_shell),
389 AMTK_MENU_SHELL_KEY,
390 amtk_menu_shell,
391 g_object_unref);
392 }
393
394 g_return_val_if_fail (AMTK_IS_MENU_SHELL (amtk_menu_shell), NULL);
395 return amtk_menu_shell;
396 }
397
398 /**
399 * amtk_menu_shell_get_menu_shell:
400 * @amtk_menu_shell: an #AmtkMenuShell.
401 *
402 * Returns: (transfer none): the #GtkMenuShell of @amtk_menu_shell.
403 * Since: 2.0
404 */
405 GtkMenuShell *
amtk_menu_shell_get_menu_shell(AmtkMenuShell * amtk_menu_shell)406 amtk_menu_shell_get_menu_shell (AmtkMenuShell *amtk_menu_shell)
407 {
408 g_return_val_if_fail (AMTK_IS_MENU_SHELL (amtk_menu_shell), NULL);
409
410 return amtk_menu_shell->priv->gtk_menu_shell;
411 }
412
413 /* ex:set ts=8 noet: */
414