1 /* GTK - The GIMP Toolkit
2 * Copyright © 2014 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19 #include "gtkpopovermenu.h"
20 #include "gtkstack.h"
21 #include "gtkstylecontext.h"
22 #include "gtkintl.h"
23
24
25 /**
26 * SECTION:gtkpopovermenu
27 * @Short_description: Popovers to use as menus
28 * @Title: GtkPopoverMenu
29 *
30 * GtkPopoverMenu is a subclass of #GtkPopover that treats its
31 * children like menus and allows switching between them. It is
32 * meant to be used primarily together with #GtkModelButton, but
33 * any widget can be used, such as #GtkSpinButton or #GtkScale.
34 * In this respect, GtkPopoverMenu is more flexible than popovers
35 * that are created from a #GMenuModel with gtk_popover_new_from_model().
36 *
37 * To add a child as a submenu, set the #GtkPopoverMenu:submenu
38 * child property to the name of the submenu. To let the user open
39 * this submenu, add a #GtkModelButton whose #GtkModelButton:menu-name
40 * property is set to the name you've given to the submenu.
41 *
42 * By convention, the first child of a submenu should be a #GtkModelButton
43 * to switch back to the parent menu. Such a button should use the
44 * #GtkModelButton:inverted and #GtkModelButton:centered properties
45 * to achieve a title-like appearance and place the submenu indicator
46 * at the opposite side. To switch back to the main menu, use "main"
47 * as the menu name.
48 *
49 * # Example
50 *
51 * |[<!-- language="xml" -->
52 * <object class="GtkPopoverMenu">
53 * <child>
54 * <object class="GtkBox">
55 * <property name="visible">True</property>
56 * <property name="margin">10</property>
57 * <child>
58 * <object class="GtkModelButton">
59 * <property name="visible">True</property>
60 * <property name="action-name">win.frob</property>
61 * <property name="text" translatable="yes">Frob</property>
62 * </object>
63 * </child>
64 * <child>
65 * <object class="GtkModelButton">
66 * <property name="visible">True</property>
67 * <property name="menu-name">more</property>
68 * <property name="text" translatable="yes">More</property>
69 * </object>
70 * </child>
71 * </object>
72 * </child>
73 * <child>
74 * <object class="GtkBox">
75 * <property name="visible">True</property>
76 * <property name="margin">10</property>
77 * <child>
78 * <object class="GtkModelButton">
79 * <property name="visible">True</property>
80 * <property name="action-name">win.foo</property>
81 * <property name="text" translatable="yes">Foo</property>
82 * </object>
83 * </child>
84 * <child>
85 * <object class="GtkModelButton">
86 * <property name="visible">True</property>
87 * <property name="action-name">win.bar</property>
88 * <property name="text" translatable="yes">Bar</property>
89 * </object>
90 * </child>
91 * </object>
92 * <packing>
93 * <property name="submenu">more</property>
94 * </packing>
95 * </child>
96 * </object>
97 * ]|
98 *
99 * Just like normal popovers created using gtk_popover_new_from_model,
100 * #GtkPopoverMenu instances have a single css node called "popover"
101 * and get the .menu style class.
102 */
103
104 struct _GtkPopoverMenu
105 {
106 GtkPopover parent_instance;
107 };
108
109 enum {
110 PROP_VISIBLE_SUBMENU = 1
111 };
112
113 enum {
114 CHILD_PROP_SUBMENU = 1,
115 CHILD_PROP_POSITION
116 };
117
G_DEFINE_TYPE(GtkPopoverMenu,gtk_popover_menu,GTK_TYPE_POPOVER)118 G_DEFINE_TYPE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER)
119
120 static void
121 visible_submenu_changed (GObject *object,
122 GParamSpec *pspec,
123 GtkPopoverMenu *popover)
124 {
125 g_object_notify (G_OBJECT (popover), "visible-submenu");
126 }
127
128 static void
gtk_popover_menu_init(GtkPopoverMenu * popover)129 gtk_popover_menu_init (GtkPopoverMenu *popover)
130 {
131 GtkWidget *stack;
132 GtkStyleContext *style_context;
133
134 stack = gtk_stack_new ();
135 gtk_stack_set_vhomogeneous (GTK_STACK (stack), FALSE);
136 gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
137 gtk_stack_set_interpolate_size (GTK_STACK (stack), TRUE);
138 gtk_widget_show (stack);
139 gtk_container_add (GTK_CONTAINER (popover), stack);
140 g_signal_connect (stack, "notify::visible-child-name",
141 G_CALLBACK (visible_submenu_changed), popover);
142
143 style_context = gtk_widget_get_style_context (GTK_WIDGET (popover));
144 gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_MENU);
145 }
146
147 static void
gtk_popover_menu_map(GtkWidget * widget)148 gtk_popover_menu_map (GtkWidget *widget)
149 {
150 GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->map (widget);
151 gtk_popover_menu_open_submenu (GTK_POPOVER_MENU (widget), "main");
152 }
153
154 static void
gtk_popover_menu_unmap(GtkWidget * widget)155 gtk_popover_menu_unmap (GtkWidget *widget)
156 {
157 gtk_popover_menu_open_submenu (GTK_POPOVER_MENU (widget), "main");
158 GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->unmap (widget);
159 }
160
161 static void
gtk_popover_menu_add(GtkContainer * container,GtkWidget * child)162 gtk_popover_menu_add (GtkContainer *container,
163 GtkWidget *child)
164 {
165 GtkWidget *stack;
166
167 stack = gtk_bin_get_child (GTK_BIN (container));
168
169 if (stack == NULL)
170 {
171 gtk_widget_set_parent (child, GTK_WIDGET (container));
172 _gtk_bin_set_child (GTK_BIN (container), child);
173 }
174 else
175 {
176 gchar *name;
177
178 if (gtk_stack_get_child_by_name (GTK_STACK (stack), "main"))
179 name = "submenu";
180 else
181 name = "main";
182
183 gtk_stack_add_named (GTK_STACK (stack), child, name);
184 }
185 }
186
187 static void
gtk_popover_menu_remove(GtkContainer * container,GtkWidget * child)188 gtk_popover_menu_remove (GtkContainer *container,
189 GtkWidget *child)
190 {
191 GtkWidget *stack;
192
193 stack = gtk_bin_get_child (GTK_BIN (container));
194
195 if (child == stack)
196 GTK_CONTAINER_CLASS (gtk_popover_menu_parent_class)->remove (container, child);
197 else
198 gtk_container_remove (GTK_CONTAINER (stack), child);
199 }
200
201 static void
gtk_popover_menu_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)202 gtk_popover_menu_forall (GtkContainer *container,
203 gboolean include_internals,
204 GtkCallback callback,
205 gpointer callback_data)
206 {
207 GtkWidget *stack;
208
209 stack = gtk_bin_get_child (GTK_BIN (container));
210
211 if (include_internals)
212 (* callback) (stack, callback_data);
213
214 gtk_container_forall (GTK_CONTAINER (stack), callback, callback_data);
215 }
216
217 static void
gtk_popover_menu_get_child_property(GtkContainer * container,GtkWidget * child,guint property_id,GValue * value,GParamSpec * pspec)218 gtk_popover_menu_get_child_property (GtkContainer *container,
219 GtkWidget *child,
220 guint property_id,
221 GValue *value,
222 GParamSpec *pspec)
223 {
224 GtkWidget *stack;
225
226 stack = gtk_bin_get_child (GTK_BIN (container));
227
228 if (child == stack)
229 return;
230
231 switch (property_id)
232 {
233 case CHILD_PROP_SUBMENU:
234 {
235 gchar *name;
236 gtk_container_child_get (GTK_CONTAINER (stack), child, "name", &name, NULL);
237 g_value_set_string (value, name);
238 }
239 break;
240
241 case CHILD_PROP_POSITION:
242 {
243 gint position;
244 gtk_container_child_get (GTK_CONTAINER (stack), child, "position", &position, NULL);
245 g_value_set_int (value, position);
246 }
247 break;
248
249 default:
250 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
251 break;
252 }
253 }
254
255 static void
gtk_popover_menu_set_child_property(GtkContainer * container,GtkWidget * child,guint property_id,const GValue * value,GParamSpec * pspec)256 gtk_popover_menu_set_child_property (GtkContainer *container,
257 GtkWidget *child,
258 guint property_id,
259 const GValue *value,
260 GParamSpec *pspec)
261 {
262 GtkWidget *stack;
263
264 stack = gtk_bin_get_child (GTK_BIN (container));
265
266 if (child == stack)
267 return;
268
269 switch (property_id)
270 {
271 case CHILD_PROP_SUBMENU:
272 {
273 const gchar *name;
274 name = g_value_get_string (value);
275 gtk_container_child_set (GTK_CONTAINER (stack), child, "name", name, NULL);
276 }
277 break;
278
279 case CHILD_PROP_POSITION:
280 {
281 gint position;
282 position = g_value_get_int (value);
283 gtk_container_child_set (GTK_CONTAINER (stack), child, "position", position, NULL);
284 }
285 break;
286
287 default:
288 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
289 break;
290 }
291 }
292
293 static void
gtk_popover_menu_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)294 gtk_popover_menu_get_property (GObject *object,
295 guint property_id,
296 GValue *value,
297 GParamSpec *pspec)
298 {
299 GtkWidget *stack;
300
301 stack = gtk_bin_get_child (GTK_BIN (object));
302
303 switch (property_id)
304 {
305 case PROP_VISIBLE_SUBMENU:
306 g_value_set_string (value, gtk_stack_get_visible_child_name (GTK_STACK (stack)));
307 break;
308
309 default:
310 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
311 break;
312 }
313 }
314
315 static void
gtk_popover_menu_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)316 gtk_popover_menu_set_property (GObject *object,
317 guint property_id,
318 const GValue *value,
319 GParamSpec *pspec)
320 {
321 GtkWidget *stack;
322
323 stack = gtk_bin_get_child (GTK_BIN (object));
324
325 switch (property_id)
326 {
327 case PROP_VISIBLE_SUBMENU:
328 gtk_stack_set_visible_child_name (GTK_STACK (stack), g_value_get_string (value));
329 break;
330
331 default:
332 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
333 break;
334 }
335 }
336
337 static void
gtk_popover_menu_class_init(GtkPopoverMenuClass * klass)338 gtk_popover_menu_class_init (GtkPopoverMenuClass *klass)
339 {
340 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
341 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
342 GObjectClass *object_class = G_OBJECT_CLASS (klass);
343
344 object_class->set_property = gtk_popover_menu_set_property;
345 object_class->get_property = gtk_popover_menu_get_property;
346
347 widget_class->map = gtk_popover_menu_map;
348 widget_class->unmap = gtk_popover_menu_unmap;
349
350 container_class->add = gtk_popover_menu_add;
351 container_class->remove = gtk_popover_menu_remove;
352 container_class->forall = gtk_popover_menu_forall;
353 container_class->set_child_property = gtk_popover_menu_set_child_property;
354 container_class->get_child_property = gtk_popover_menu_get_child_property;
355
356 g_object_class_install_property (object_class,
357 PROP_VISIBLE_SUBMENU,
358 g_param_spec_string ("visible-submenu",
359 P_("Visible submenu"),
360 P_("The name of the visible submenu"),
361 NULL,
362 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
363
364 /**
365 * GtkPopoverMenu:submenu:
366 *
367 * The submenu child property specifies the name of the submenu
368 * If it is %NULL or "main", the child is used as the main menu,
369 * which is shown initially when the popover is mapped.
370 *
371 * Since: 3.16
372 */
373 gtk_container_class_install_child_property (container_class,
374 CHILD_PROP_SUBMENU,
375 g_param_spec_string ("submenu",
376 P_("Submenu"),
377 P_("The name of the submenu"),
378 NULL,
379 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
380
381 gtk_container_class_install_child_property (container_class,
382 CHILD_PROP_POSITION,
383 g_param_spec_int ("position",
384 P_("Position"),
385 P_("The index of the child in the parent"),
386 -1, G_MAXINT, 0,
387 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
388 }
389
390 /**
391 * gtk_popover_menu_new:
392 *
393 * Creates a new popover menu.
394 *
395 * Returns: a new #GtkPopoverMenu
396 *
397 * Since: 3.16
398 */
399 GtkWidget *
gtk_popover_menu_new(void)400 gtk_popover_menu_new (void)
401 {
402 return g_object_new (GTK_TYPE_POPOVER_MENU, NULL);
403 }
404
405 /**
406 * gtk_popover_menu_open_submenu:
407 * @popover: a #GtkPopoverMenu
408 * @name: the name of the menu to switch to
409 *
410 * Opens a submenu of the @popover. The @name
411 * must be one of the names given to the submenus
412 * of @popover with #GtkPopoverMenu:submenu, or
413 * "main" to switch back to the main menu.
414 *
415 * #GtkModelButton will open submenus automatically
416 * when the #GtkModelButton:menu-name property is set,
417 * so this function is only needed when you are using
418 * other kinds of widgets to initiate menu changes.
419 *
420 * Since: 3.16
421 */
422 void
gtk_popover_menu_open_submenu(GtkPopoverMenu * popover,const gchar * name)423 gtk_popover_menu_open_submenu (GtkPopoverMenu *popover,
424 const gchar *name)
425 {
426 GtkWidget *stack;
427
428 g_return_if_fail (GTK_IS_POPOVER_MENU (popover));
429
430 stack = gtk_bin_get_child (GTK_BIN (popover));
431 gtk_stack_set_visible_child_name (GTK_STACK (stack), name);
432 }
433