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