1 /* GTK - The GIMP Toolkit
2 *
3 * Copyright (C) 2003 Ricardo Fernandez Pascual
4 * Copyright (C) 2004 Paolo Borelli
5 * Copyright (C) 2012 Bastien Nocera
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /**
22 * SECTION:gtkmenubutton
23 * @short_description: A widget that shows a popup when clicked on
24 * @title: GtkMenuButton
25 *
26 * The #GtkMenuButton widget is used to display a popup when clicked on.
27 * This popup can be provided either as a #GtkMenu, a #GtkPopover or an
28 * abstract #GMenuModel.
29 *
30 * The #GtkMenuButton widget can hold any valid child widget. That is, it
31 * can hold almost any other standard #GtkWidget. The most commonly used
32 * child is #GtkImage. If no widget is explicitely added to the #GtkMenuButton,
33 * a #GtkImage is automatically created, using an arrow image oriented
34 * according to #GtkMenuButton:direction or the generic “open-menu-symbolic”
35 * icon if the direction is not set.
36 *
37 * The positioning of the popup is determined by the #GtkMenuButton:direction
38 * property of the menu button.
39 *
40 * For menus, the #GtkWidget:halign and #GtkWidget:valign properties of the
41 * menu are also taken into account. For example, when the direction is
42 * %GTK_ARROW_DOWN and the horizontal alignment is %GTK_ALIGN_START, the
43 * menu will be positioned below the button, with the starting edge
44 * (depending on the text direction) of the menu aligned with the starting
45 * edge of the button. If there is not enough space below the button, the
46 * menu is popped up above the button instead. If the alignment would move
47 * part of the menu offscreen, it is “pushed in”.
48 *
49 * ## Direction = Down
50 *
51 * - halign = start
52 *
53 * ![](down-start.png)
54 *
55 * - halign = center
56 *
57 * ![](down-center.png)
58 *
59 * - halign = end
60 *
61 * ![](down-end.png)
62 *
63 * ## Direction = Up
64 *
65 * - halign = start
66 *
67 * ![](up-start.png)
68 *
69 * - halign = center
70 *
71 * ![](up-center.png)
72 *
73 * - halign = end
74 *
75 * ![](up-end.png)
76 *
77 * ## Direction = Left
78 *
79 * - valign = start
80 *
81 * ![](left-start.png)
82 *
83 * - valign = center
84 *
85 * ![](left-center.png)
86 *
87 * - valign = end
88 *
89 * ![](left-end.png)
90 *
91 * ## Direction = Right
92 *
93 * - valign = start
94 *
95 * ![](right-start.png)
96 *
97 * - valign = center
98 *
99 * ![](right-center.png)
100 *
101 * - valign = end
102 *
103 * ![](right-end.png)
104 *
105 * # CSS nodes
106 *
107 * GtkMenuButton has a single CSS node with name button. To differentiate
108 * it from a plain #GtkButton, it gets the .popup style class.
109 */
110
111 #include "config.h"
112
113 #include "gtkmenubutton.h"
114 #include "gtkmenubuttonprivate.h"
115 #include "gtkbuttonprivate.h"
116 #include "gtktypebuiltins.h"
117 #include "gtkwindow.h"
118 #include "gtkmain.h"
119 #include "gtkaccessible.h"
120 #include "gtkpopover.h"
121 #include "a11y/gtkmenubuttonaccessible.h"
122
123 #include "gtkprivate.h"
124 #include "gtkintl.h"
125
126 struct _GtkMenuButtonPrivate
127 {
128 GtkWidget *menu; /* The menu and the popover are mutually exclusive */
129 GtkWidget *popover; /* Only one at a time can be set */
130 GMenuModel *model;
131
132 GtkMenuButtonShowMenuCallback func;
133 gpointer user_data;
134
135 GtkWidget *align_widget;
136 GtkWidget *arrow_widget;
137 GtkArrowType arrow_type;
138 gboolean use_popover;
139 guint press_handled : 1;
140 };
141
142 enum
143 {
144 PROP_0,
145 PROP_POPUP,
146 PROP_MENU_MODEL,
147 PROP_ALIGN_WIDGET,
148 PROP_DIRECTION,
149 PROP_USE_POPOVER,
150 PROP_POPOVER,
151 LAST_PROP
152 };
153
154 static GParamSpec *menu_button_props[LAST_PROP];
155
156 G_DEFINE_TYPE_WITH_PRIVATE (GtkMenuButton, gtk_menu_button, GTK_TYPE_TOGGLE_BUTTON)
157
158 static void gtk_menu_button_dispose (GObject *object);
159
160 static void
gtk_menu_button_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)161 gtk_menu_button_set_property (GObject *object,
162 guint property_id,
163 const GValue *value,
164 GParamSpec *pspec)
165 {
166 GtkMenuButton *self = GTK_MENU_BUTTON (object);
167
168 switch (property_id)
169 {
170 case PROP_POPUP:
171 gtk_menu_button_set_popup (self, g_value_get_object (value));
172 break;
173 case PROP_MENU_MODEL:
174 gtk_menu_button_set_menu_model (self, g_value_get_object (value));
175 break;
176 case PROP_ALIGN_WIDGET:
177 gtk_menu_button_set_align_widget (self, g_value_get_object (value));
178 break;
179 case PROP_DIRECTION:
180 gtk_menu_button_set_direction (self, g_value_get_enum (value));
181 break;
182 case PROP_USE_POPOVER:
183 gtk_menu_button_set_use_popover (self, g_value_get_boolean (value));
184 break;
185 case PROP_POPOVER:
186 gtk_menu_button_set_popover (self, g_value_get_object (value));
187 break;
188 default:
189 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
190 }
191 }
192
193 static void
gtk_menu_button_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)194 gtk_menu_button_get_property (GObject *object,
195 guint property_id,
196 GValue *value,
197 GParamSpec *pspec)
198 {
199 GtkMenuButtonPrivate *priv = GTK_MENU_BUTTON (object)->priv;
200
201 switch (property_id)
202 {
203 case PROP_POPUP:
204 g_value_set_object (value, priv->menu);
205 break;
206 case PROP_MENU_MODEL:
207 g_value_set_object (value, priv->model);
208 break;
209 case PROP_ALIGN_WIDGET:
210 g_value_set_object (value, priv->align_widget);
211 break;
212 case PROP_DIRECTION:
213 g_value_set_enum (value, priv->arrow_type);
214 break;
215 case PROP_USE_POPOVER:
216 g_value_set_boolean (value, priv->use_popover);
217 break;
218 case PROP_POPOVER:
219 g_value_set_object (value, priv->popover);
220 break;
221 default:
222 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
223 }
224 }
225
226 static void
gtk_menu_button_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state_flags)227 gtk_menu_button_state_flags_changed (GtkWidget *widget,
228 GtkStateFlags previous_state_flags)
229 {
230 GtkMenuButton *button = GTK_MENU_BUTTON (widget);
231 GtkMenuButtonPrivate *priv = button->priv;
232
233 if (!gtk_widget_is_sensitive (widget))
234 {
235 if (priv->menu)
236 gtk_menu_shell_deactivate (GTK_MENU_SHELL (priv->menu));
237 else if (priv->popover)
238 gtk_widget_hide (priv->popover);
239 }
240 }
241
242 static void
popup_menu(GtkMenuButton * menu_button,GdkEvent * event)243 popup_menu (GtkMenuButton *menu_button,
244 GdkEvent *event)
245 {
246 GtkMenuButtonPrivate *priv = menu_button->priv;
247 GdkGravity widget_anchor = GDK_GRAVITY_SOUTH_WEST;
248 GdkGravity menu_anchor = GDK_GRAVITY_NORTH_WEST;
249
250 if (priv->func)
251 priv->func (priv->user_data);
252
253 if (!priv->menu)
254 return;
255
256 switch (priv->arrow_type)
257 {
258 case GTK_ARROW_UP:
259 g_object_set (priv->menu,
260 "anchor-hints", (GDK_ANCHOR_FLIP_Y |
261 GDK_ANCHOR_SLIDE |
262 GDK_ANCHOR_RESIZE),
263 NULL);
264
265 switch (gtk_widget_get_halign (priv->menu))
266 {
267 case GTK_ALIGN_FILL:
268 case GTK_ALIGN_START:
269 case GTK_ALIGN_BASELINE:
270 widget_anchor = GDK_GRAVITY_NORTH_WEST;
271 menu_anchor = GDK_GRAVITY_SOUTH_WEST;
272 break;
273
274 case GTK_ALIGN_END:
275 widget_anchor = GDK_GRAVITY_NORTH_EAST;
276 menu_anchor = GDK_GRAVITY_SOUTH_EAST;
277 break;
278
279 case GTK_ALIGN_CENTER:
280 widget_anchor = GDK_GRAVITY_NORTH;
281 menu_anchor = GDK_GRAVITY_SOUTH;
282 break;
283 }
284
285 break;
286
287 case GTK_ARROW_DOWN:
288 /* In the common case the menu button is showing a dropdown menu, set the
289 * corresponding type hint on the toplevel, so the WM can omit the top side
290 * of the shadows.
291 */
292 g_object_set (priv->menu,
293 "anchor-hints", (GDK_ANCHOR_FLIP_Y |
294 GDK_ANCHOR_SLIDE |
295 GDK_ANCHOR_RESIZE),
296 "menu-type-hint", GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU,
297 NULL);
298
299 switch (gtk_widget_get_halign (priv->menu))
300 {
301 case GTK_ALIGN_FILL:
302 case GTK_ALIGN_START:
303 case GTK_ALIGN_BASELINE:
304 widget_anchor = GDK_GRAVITY_SOUTH_WEST;
305 menu_anchor = GDK_GRAVITY_NORTH_WEST;
306 break;
307
308 case GTK_ALIGN_END:
309 widget_anchor = GDK_GRAVITY_SOUTH_EAST;
310 menu_anchor = GDK_GRAVITY_NORTH_EAST;
311 break;
312
313 case GTK_ALIGN_CENTER:
314 widget_anchor = GDK_GRAVITY_SOUTH;
315 menu_anchor = GDK_GRAVITY_NORTH;
316 break;
317 }
318
319 break;
320
321 case GTK_ARROW_LEFT:
322 g_object_set (priv->menu,
323 "anchor-hints", (GDK_ANCHOR_FLIP_X |
324 GDK_ANCHOR_SLIDE |
325 GDK_ANCHOR_RESIZE),
326 NULL);
327
328 switch (gtk_widget_get_valign (priv->menu))
329 {
330 case GTK_ALIGN_FILL:
331 case GTK_ALIGN_START:
332 case GTK_ALIGN_BASELINE:
333 widget_anchor = GDK_GRAVITY_NORTH_WEST;
334 menu_anchor = GDK_GRAVITY_NORTH_EAST;
335 break;
336
337 case GTK_ALIGN_END:
338 widget_anchor = GDK_GRAVITY_SOUTH_WEST;
339 menu_anchor = GDK_GRAVITY_SOUTH_EAST;
340 break;
341
342 case GTK_ALIGN_CENTER:
343 widget_anchor = GDK_GRAVITY_WEST;
344 menu_anchor = GDK_GRAVITY_EAST;
345 break;
346 }
347
348 break;
349
350 case GTK_ARROW_RIGHT:
351 g_object_set (priv->menu,
352 "anchor-hints", (GDK_ANCHOR_FLIP_X |
353 GDK_ANCHOR_SLIDE |
354 GDK_ANCHOR_RESIZE),
355 NULL);
356
357 switch (gtk_widget_get_valign (priv->menu))
358 {
359 case GTK_ALIGN_FILL:
360 case GTK_ALIGN_START:
361 case GTK_ALIGN_BASELINE:
362 widget_anchor = GDK_GRAVITY_NORTH_EAST;
363 menu_anchor = GDK_GRAVITY_NORTH_WEST;
364 break;
365
366 case GTK_ALIGN_END:
367 widget_anchor = GDK_GRAVITY_SOUTH_EAST;
368 menu_anchor = GDK_GRAVITY_SOUTH_WEST;
369 break;
370
371 case GTK_ALIGN_CENTER:
372 widget_anchor = GDK_GRAVITY_EAST;
373 menu_anchor = GDK_GRAVITY_WEST;
374 break;
375 }
376
377 break;
378
379 case GTK_ARROW_NONE:
380 g_object_set (priv->menu,
381 "anchor-hints", (GDK_ANCHOR_FLIP_Y |
382 GDK_ANCHOR_SLIDE |
383 GDK_ANCHOR_RESIZE),
384 NULL);
385
386 break;
387 }
388
389 gtk_menu_popup_at_widget (GTK_MENU (priv->menu),
390 GTK_WIDGET (menu_button),
391 widget_anchor,
392 menu_anchor,
393 event);
394 }
395
396 static void
gtk_menu_button_toggled(GtkToggleButton * button)397 gtk_menu_button_toggled (GtkToggleButton *button)
398 {
399 GtkMenuButton *menu_button = GTK_MENU_BUTTON (button);
400 GtkMenuButtonPrivate *priv = menu_button->priv;
401 gboolean active = gtk_toggle_button_get_active (button);
402
403 if (priv->menu)
404 {
405 if (active && !gtk_widget_get_visible (priv->menu))
406 {
407 GdkEvent *event;
408
409 event = gtk_get_current_event ();
410
411 popup_menu (menu_button, event);
412
413 if (!event ||
414 event->type == GDK_KEY_PRESS ||
415 event->type == GDK_KEY_RELEASE)
416 gtk_menu_shell_select_first (GTK_MENU_SHELL (priv->menu), FALSE);
417
418 if (event)
419 gdk_event_free (event);
420 }
421 }
422 else if (priv->popover)
423 {
424 if (active)
425 gtk_popover_popup (GTK_POPOVER (priv->popover));
426 else
427 gtk_popover_popdown (GTK_POPOVER (priv->popover));
428 }
429
430 if (GTK_TOGGLE_BUTTON_CLASS (gtk_menu_button_parent_class)->toggled)
431 GTK_TOGGLE_BUTTON_CLASS (gtk_menu_button_parent_class)->toggled (button);
432 }
433
434 static void
gtk_menu_button_add(GtkContainer * container,GtkWidget * child)435 gtk_menu_button_add (GtkContainer *container,
436 GtkWidget *child)
437 {
438 GtkMenuButton *button = GTK_MENU_BUTTON (container);
439
440 if (button->priv->arrow_widget)
441 gtk_container_remove (container, button->priv->arrow_widget);
442
443 GTK_CONTAINER_CLASS (gtk_menu_button_parent_class)->add (container, child);
444 }
445
446 static void
gtk_menu_button_remove(GtkContainer * container,GtkWidget * child)447 gtk_menu_button_remove (GtkContainer *container,
448 GtkWidget *child)
449 {
450 GtkMenuButton *button = GTK_MENU_BUTTON (container);
451
452 if (child == button->priv->arrow_widget)
453 button->priv->arrow_widget = NULL;
454
455 GTK_CONTAINER_CLASS (gtk_menu_button_parent_class)->remove (container, child);
456 }
457
458 static void
gtk_menu_button_class_init(GtkMenuButtonClass * klass)459 gtk_menu_button_class_init (GtkMenuButtonClass *klass)
460 {
461 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
462 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
463 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
464 GtkToggleButtonClass *toggle_button_class = GTK_TOGGLE_BUTTON_CLASS (klass);
465
466 gobject_class->set_property = gtk_menu_button_set_property;
467 gobject_class->get_property = gtk_menu_button_get_property;
468 gobject_class->dispose = gtk_menu_button_dispose;
469
470 widget_class->state_flags_changed = gtk_menu_button_state_flags_changed;
471
472 container_class->add = gtk_menu_button_add;
473 container_class->remove = gtk_menu_button_remove;
474
475 toggle_button_class->toggled = gtk_menu_button_toggled;
476
477 /**
478 * GtkMenuButton:popup:
479 *
480 * The #GtkMenu that will be popped up when the button is clicked.
481 *
482 * Since: 3.6
483 */
484 menu_button_props[PROP_POPUP] =
485 g_param_spec_object ("popup",
486 P_("Popup"),
487 P_("The dropdown menu."),
488 GTK_TYPE_MENU,
489 GTK_PARAM_READWRITE);
490
491 /**
492 * GtkMenuButton:menu-model:
493 *
494 * The #GMenuModel from which the popup will be created.
495 * Depending on the #GtkMenuButton:use-popover property, that may
496 * be a menu or a popover.
497 *
498 * See gtk_menu_button_set_menu_model() for the interaction with the
499 * #GtkMenuButton:popup property.
500 *
501 * Since: 3.6
502 */
503 menu_button_props[PROP_MENU_MODEL] =
504 g_param_spec_object ("menu-model",
505 P_("Menu model"),
506 P_("The model from which the popup is made."),
507 G_TYPE_MENU_MODEL,
508 GTK_PARAM_READWRITE);
509
510 /**
511 * GtkMenuButton:align-widget:
512 *
513 * The #GtkWidget to use to align the menu with.
514 *
515 * Since: 3.6
516 */
517 menu_button_props[PROP_ALIGN_WIDGET] =
518 g_param_spec_object ("align-widget",
519 P_("Align with"),
520 P_("The parent widget which the menu should align with."),
521 GTK_TYPE_CONTAINER,
522 GTK_PARAM_READWRITE);
523
524 /**
525 * GtkMenuButton:direction:
526 *
527 * The #GtkArrowType representing the direction in which the
528 * menu or popover will be popped out.
529 *
530 * Since: 3.6
531 */
532 menu_button_props[PROP_DIRECTION] =
533 g_param_spec_enum ("direction",
534 P_("Direction"),
535 P_("The direction the arrow should point."),
536 GTK_TYPE_ARROW_TYPE,
537 GTK_ARROW_DOWN,
538 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
539
540 /**
541 * GtkMenuButton:use-popover:
542 *
543 * Whether to construct a #GtkPopover from the menu model,
544 * or a #GtkMenu.
545 *
546 * Since: 3.12
547 */
548 menu_button_props[PROP_USE_POPOVER] =
549 g_param_spec_boolean ("use-popover",
550 P_("Use a popover"),
551 P_("Use a popover instead of a menu"),
552 TRUE,
553 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
554
555 /**
556 * GtkMenuButton:popover:
557 *
558 * The #GtkPopover that will be popped up when the button is clicked.
559 *
560 * Since: 3.12
561 */
562 menu_button_props[PROP_POPOVER] =
563 g_param_spec_object ("popover",
564 P_("Popover"),
565 P_("The popover"),
566 GTK_TYPE_POPOVER,
567 G_PARAM_READWRITE);
568
569 g_object_class_install_properties (gobject_class, LAST_PROP, menu_button_props);
570
571 gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_MENU_BUTTON_ACCESSIBLE);
572 gtk_widget_class_set_css_name (widget_class, "button");
573 }
574
575 static void
set_arrow_type(GtkImage * image,GtkArrowType arrow_type)576 set_arrow_type (GtkImage *image,
577 GtkArrowType arrow_type)
578 {
579 switch (arrow_type)
580 {
581 case GTK_ARROW_NONE:
582 gtk_image_set_from_icon_name (image, "open-menu-symbolic", GTK_ICON_SIZE_BUTTON);
583 break;
584 case GTK_ARROW_DOWN:
585 gtk_image_set_from_icon_name (image, "pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
586 break;
587 case GTK_ARROW_UP:
588 gtk_image_set_from_icon_name (image, "pan-up-symbolic", GTK_ICON_SIZE_BUTTON);
589 break;
590 case GTK_ARROW_LEFT:
591 gtk_image_set_from_icon_name (image, "pan-start-symbolic", GTK_ICON_SIZE_BUTTON);
592 break;
593 case GTK_ARROW_RIGHT:
594 gtk_image_set_from_icon_name (image, "pan-end-symbolic", GTK_ICON_SIZE_BUTTON);
595 break;
596 }
597 }
598
599 static void
add_arrow(GtkMenuButton * menu_button)600 add_arrow (GtkMenuButton *menu_button)
601 {
602 GtkWidget *arrow;
603
604 arrow = gtk_image_new ();
605 set_arrow_type (GTK_IMAGE (arrow), menu_button->priv->arrow_type);
606 gtk_container_add (GTK_CONTAINER (menu_button), arrow);
607 gtk_widget_show (arrow);
608 menu_button->priv->arrow_widget = arrow;
609 }
610
611 static void
gtk_menu_button_init(GtkMenuButton * menu_button)612 gtk_menu_button_init (GtkMenuButton *menu_button)
613 {
614 GtkMenuButtonPrivate *priv;
615 GtkStyleContext *context;
616
617 priv = gtk_menu_button_get_instance_private (menu_button);
618 menu_button->priv = priv;
619 priv->arrow_type = GTK_ARROW_DOWN;
620 priv->use_popover = TRUE;
621
622 add_arrow (menu_button);
623
624 gtk_widget_set_focus_on_click (GTK_WIDGET (menu_button), FALSE);
625 gtk_widget_set_sensitive (GTK_WIDGET (menu_button), FALSE);
626
627 context = gtk_widget_get_style_context (GTK_WIDGET (menu_button));
628 gtk_style_context_add_class (context, "popup");
629 }
630
631 /**
632 * gtk_menu_button_new:
633 *
634 * Creates a new #GtkMenuButton widget with downwards-pointing
635 * arrow as the only child. You can replace the child widget
636 * with another #GtkWidget should you wish to.
637 *
638 * Returns: The newly created #GtkMenuButton widget
639 *
640 * Since: 3.6
641 */
642 GtkWidget *
gtk_menu_button_new(void)643 gtk_menu_button_new (void)
644 {
645 return g_object_new (GTK_TYPE_MENU_BUTTON, NULL);
646 }
647
648 /* Callback for the "deactivate" signal on the pop-up menu.
649 * This is used so that we unset the state of the toggle button
650 * when the pop-up menu disappears.
651 * Also used for the "close" signal on the popover.
652 */
653 static gboolean
menu_deactivate_cb(GtkMenuButton * menu_button)654 menu_deactivate_cb (GtkMenuButton *menu_button)
655 {
656 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button), FALSE);
657 gtk_widget_unset_state_flags (GTK_WIDGET (menu_button), GTK_STATE_FLAG_PRELIGHT);
658
659 return TRUE;
660 }
661
662 static void
menu_detacher(GtkWidget * widget,GtkMenu * menu)663 menu_detacher (GtkWidget *widget,
664 GtkMenu *menu)
665 {
666 GtkMenuButtonPrivate *priv = GTK_MENU_BUTTON (widget)->priv;
667
668 g_return_if_fail (priv->menu == (GtkWidget *) menu);
669
670 priv->menu = NULL;
671 }
672
673 static void
update_sensitivity(GtkMenuButton * menu_button)674 update_sensitivity (GtkMenuButton *menu_button)
675 {
676 GtkMenuButtonPrivate *priv = menu_button->priv;
677
678 if (GTK_BUTTON (menu_button)->priv->action_helper)
679 return;
680
681 gtk_widget_set_sensitive (GTK_WIDGET (menu_button),
682 priv->menu != NULL || priv->popover != NULL);
683 }
684
685 /* This function is used in GtkMenuToolButton, the call back will
686 * be called when GtkMenuToolButton would have emitted the “show-menu”
687 * signal.
688 */
689 void
_gtk_menu_button_set_popup_with_func(GtkMenuButton * menu_button,GtkWidget * menu,GtkMenuButtonShowMenuCallback func,gpointer user_data)690 _gtk_menu_button_set_popup_with_func (GtkMenuButton *menu_button,
691 GtkWidget *menu,
692 GtkMenuButtonShowMenuCallback func,
693 gpointer user_data)
694 {
695 GtkMenuButtonPrivate *priv;
696
697 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
698 g_return_if_fail (GTK_IS_MENU (menu) || menu == NULL);
699
700 priv = menu_button->priv;
701 priv->func = func;
702 priv->user_data = user_data;
703
704 if (priv->menu == GTK_WIDGET (menu))
705 return;
706
707 if (priv->menu)
708 {
709 if (gtk_widget_get_visible (priv->menu))
710 gtk_menu_shell_deactivate (GTK_MENU_SHELL (priv->menu));
711
712 g_signal_handlers_disconnect_by_func (priv->menu,
713 menu_deactivate_cb,
714 menu_button);
715 gtk_menu_detach (GTK_MENU (priv->menu));
716 }
717
718 priv->menu = menu;
719
720 if (priv->menu)
721 {
722 gtk_menu_attach_to_widget (GTK_MENU (priv->menu), GTK_WIDGET (menu_button),
723 menu_detacher);
724
725 gtk_widget_set_visible (priv->menu, FALSE);
726
727 g_signal_connect_swapped (priv->menu, "deactivate",
728 G_CALLBACK (menu_deactivate_cb), menu_button);
729 }
730
731 update_sensitivity (menu_button);
732
733 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_POPUP]);
734 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_MENU_MODEL]);
735 }
736
737 /**
738 * gtk_menu_button_set_popup:
739 * @menu_button: a #GtkMenuButton
740 * @menu: (nullable): a #GtkMenu, or %NULL to unset and disable the button
741 *
742 * Sets the #GtkMenu that will be popped up when the @menu_button is clicked, or
743 * %NULL to dissociate any existing menu and disable the button.
744 *
745 * If #GtkMenuButton:menu-model or #GtkMenuButton:popover are set, those objects
746 * are dissociated from the @menu_button, and those properties are set to %NULL.
747 *
748 * Since: 3.6
749 */
750 void
gtk_menu_button_set_popup(GtkMenuButton * menu_button,GtkWidget * menu)751 gtk_menu_button_set_popup (GtkMenuButton *menu_button,
752 GtkWidget *menu)
753 {
754 GtkMenuButtonPrivate *priv = menu_button->priv;
755
756 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
757 g_return_if_fail (GTK_IS_MENU (menu) || menu == NULL);
758
759 g_object_freeze_notify (G_OBJECT (menu_button));
760
761 g_clear_object (&priv->model);
762
763 _gtk_menu_button_set_popup_with_func (menu_button, menu, NULL, NULL);
764
765 if (menu && priv->popover)
766 gtk_menu_button_set_popover (menu_button, NULL);
767
768 update_sensitivity (menu_button);
769
770 g_object_thaw_notify (G_OBJECT (menu_button));
771 }
772
773 /**
774 * gtk_menu_button_get_popup:
775 * @menu_button: a #GtkMenuButton
776 *
777 * Returns the #GtkMenu that pops out of the button.
778 * If the button does not use a #GtkMenu, this function
779 * returns %NULL.
780 *
781 * Returns: (nullable) (transfer none): a #GtkMenu or %NULL
782 *
783 * Since: 3.6
784 */
785 GtkMenu *
gtk_menu_button_get_popup(GtkMenuButton * menu_button)786 gtk_menu_button_get_popup (GtkMenuButton *menu_button)
787 {
788 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
789
790 return GTK_MENU (menu_button->priv->menu);
791 }
792
793 /**
794 * gtk_menu_button_set_menu_model:
795 * @menu_button: a #GtkMenuButton
796 * @menu_model: (nullable): a #GMenuModel, or %NULL to unset and disable the
797 * button
798 *
799 * Sets the #GMenuModel from which the popup will be constructed,
800 * or %NULL to dissociate any existing menu model and disable the button.
801 *
802 * Depending on the value of #GtkMenuButton:use-popover, either a
803 * #GtkMenu will be created with gtk_menu_new_from_model(), or a
804 * #GtkPopover with gtk_popover_new_from_model(). In either case,
805 * actions will be connected as documented for these functions.
806 *
807 * If #GtkMenuButton:popup or #GtkMenuButton:popover are already set, those
808 * widgets are dissociated from the @menu_button, and those properties are set
809 * to %NULL.
810 *
811 * Since: 3.6
812 */
813 void
gtk_menu_button_set_menu_model(GtkMenuButton * menu_button,GMenuModel * menu_model)814 gtk_menu_button_set_menu_model (GtkMenuButton *menu_button,
815 GMenuModel *menu_model)
816 {
817 GtkMenuButtonPrivate *priv;
818
819 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
820 g_return_if_fail (G_IS_MENU_MODEL (menu_model) || menu_model == NULL);
821
822 priv = menu_button->priv;
823
824 g_object_freeze_notify (G_OBJECT (menu_button));
825
826 if (menu_model)
827 g_object_ref (menu_model);
828
829 if (menu_model)
830 {
831 if (priv->use_popover)
832 {
833 GtkWidget *popover;
834
835 popover = gtk_popover_new_from_model (GTK_WIDGET (menu_button), menu_model);
836 gtk_menu_button_set_popover (menu_button, popover);
837 }
838 else
839 {
840 GtkWidget *menu;
841
842 menu = gtk_menu_new_from_model (menu_model);
843 gtk_widget_show_all (menu);
844 gtk_menu_button_set_popup (menu_button, menu);
845 }
846 }
847 else
848 {
849 gtk_menu_button_set_popup (menu_button, NULL);
850 gtk_menu_button_set_popover (menu_button, NULL);
851 }
852
853 priv->model = menu_model;
854 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_MENU_MODEL]);
855
856 g_object_thaw_notify (G_OBJECT (menu_button));
857 }
858
859 /**
860 * gtk_menu_button_get_menu_model:
861 * @menu_button: a #GtkMenuButton
862 *
863 * Returns the #GMenuModel used to generate the popup.
864 *
865 * Returns: (nullable) (transfer none): a #GMenuModel or %NULL
866 *
867 * Since: 3.6
868 */
869 GMenuModel *
gtk_menu_button_get_menu_model(GtkMenuButton * menu_button)870 gtk_menu_button_get_menu_model (GtkMenuButton *menu_button)
871 {
872 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
873
874 return menu_button->priv->model;
875 }
876
877 static void
set_align_widget_pointer(GtkMenuButton * menu_button,GtkWidget * align_widget)878 set_align_widget_pointer (GtkMenuButton *menu_button,
879 GtkWidget *align_widget)
880 {
881 GtkMenuButtonPrivate *priv;
882
883 priv = menu_button->priv;
884
885 if (priv->align_widget)
886 g_object_remove_weak_pointer (G_OBJECT (priv->align_widget), (gpointer *) &priv->align_widget);
887
888 priv->align_widget = align_widget;
889
890 if (priv->align_widget)
891 g_object_add_weak_pointer (G_OBJECT (priv->align_widget), (gpointer *) &priv->align_widget);
892 }
893
894 /**
895 * gtk_menu_button_set_align_widget:
896 * @menu_button: a #GtkMenuButton
897 * @align_widget: (allow-none): a #GtkWidget
898 *
899 * Sets the #GtkWidget to use to line the menu with when popped up.
900 * Note that the @align_widget must contain the #GtkMenuButton itself.
901 *
902 * Setting it to %NULL means that the menu will be aligned with the
903 * button itself.
904 *
905 * Note that this property is only used with menus currently,
906 * and not for popovers.
907 *
908 * Since: 3.6
909 */
910 void
gtk_menu_button_set_align_widget(GtkMenuButton * menu_button,GtkWidget * align_widget)911 gtk_menu_button_set_align_widget (GtkMenuButton *menu_button,
912 GtkWidget *align_widget)
913 {
914 GtkMenuButtonPrivate *priv;
915
916 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
917 g_return_if_fail (align_widget == NULL || gtk_widget_is_ancestor (GTK_WIDGET (menu_button), align_widget));
918
919 priv = menu_button->priv;
920 if (priv->align_widget == align_widget)
921 return;
922
923 set_align_widget_pointer (menu_button, align_widget);
924
925 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_ALIGN_WIDGET]);
926 }
927
928 /**
929 * gtk_menu_button_get_align_widget:
930 * @menu_button: a #GtkMenuButton
931 *
932 * Returns the parent #GtkWidget to use to line up with menu.
933 *
934 * Returns: (nullable) (transfer none): a #GtkWidget value or %NULL
935 *
936 * Since: 3.6
937 */
938 GtkWidget *
gtk_menu_button_get_align_widget(GtkMenuButton * menu_button)939 gtk_menu_button_get_align_widget (GtkMenuButton *menu_button)
940 {
941 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
942
943 return menu_button->priv->align_widget;
944 }
945
946 static void
update_popover_direction(GtkMenuButton * menu_button)947 update_popover_direction (GtkMenuButton *menu_button)
948 {
949 GtkMenuButtonPrivate *priv = menu_button->priv;
950
951 if (!priv->popover)
952 return;
953
954 switch (priv->arrow_type)
955 {
956 case GTK_ARROW_UP:
957 gtk_popover_set_position (GTK_POPOVER (priv->popover), GTK_POS_TOP);
958 break;
959 case GTK_ARROW_DOWN:
960 case GTK_ARROW_NONE:
961 gtk_popover_set_position (GTK_POPOVER (priv->popover), GTK_POS_BOTTOM);
962 break;
963 case GTK_ARROW_LEFT:
964 gtk_popover_set_position (GTK_POPOVER (priv->popover), GTK_POS_LEFT);
965 break;
966 case GTK_ARROW_RIGHT:
967 gtk_popover_set_position (GTK_POPOVER (priv->popover), GTK_POS_RIGHT);
968 break;
969 }
970 }
971
972 static void
popover_destroy_cb(GtkMenuButton * menu_button)973 popover_destroy_cb (GtkMenuButton *menu_button)
974 {
975 gtk_menu_button_set_popover (menu_button, NULL);
976 }
977
978 /**
979 * gtk_menu_button_set_direction:
980 * @menu_button: a #GtkMenuButton
981 * @direction: a #GtkArrowType
982 *
983 * Sets the direction in which the popup will be popped up, as
984 * well as changing the arrow’s direction. The child will not
985 * be changed to an arrow if it was customized.
986 *
987 * If the does not fit in the available space in the given direction,
988 * GTK+ will its best to keep it inside the screen and fully visible.
989 *
990 * If you pass %GTK_ARROW_NONE for a @direction, the popup will behave
991 * as if you passed %GTK_ARROW_DOWN (although you won’t see any arrows).
992 *
993 * Since: 3.6
994 */
995 void
gtk_menu_button_set_direction(GtkMenuButton * menu_button,GtkArrowType direction)996 gtk_menu_button_set_direction (GtkMenuButton *menu_button,
997 GtkArrowType direction)
998 {
999 GtkMenuButtonPrivate *priv = menu_button->priv;
1000 GtkWidget *child;
1001
1002 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1003
1004 if (priv->arrow_type == direction)
1005 return;
1006
1007 priv->arrow_type = direction;
1008 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_DIRECTION]);
1009
1010 /* Is it custom content? We don't change that */
1011 child = gtk_bin_get_child (GTK_BIN (menu_button));
1012 if (priv->arrow_widget != child)
1013 return;
1014
1015 set_arrow_type (GTK_IMAGE (child), priv->arrow_type);
1016 update_popover_direction (menu_button);
1017 }
1018
1019 /**
1020 * gtk_menu_button_get_direction:
1021 * @menu_button: a #GtkMenuButton
1022 *
1023 * Returns the direction the popup will be pointing at when popped up.
1024 *
1025 * Returns: a #GtkArrowType value
1026 *
1027 * Since: 3.6
1028 */
1029 GtkArrowType
gtk_menu_button_get_direction(GtkMenuButton * menu_button)1030 gtk_menu_button_get_direction (GtkMenuButton *menu_button)
1031 {
1032 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), GTK_ARROW_DOWN);
1033
1034 return menu_button->priv->arrow_type;
1035 }
1036
1037 static void
gtk_menu_button_dispose(GObject * object)1038 gtk_menu_button_dispose (GObject *object)
1039 {
1040 GtkMenuButtonPrivate *priv = GTK_MENU_BUTTON (object)->priv;
1041
1042 if (priv->menu)
1043 {
1044 g_signal_handlers_disconnect_by_func (priv->menu,
1045 menu_deactivate_cb,
1046 object);
1047 gtk_menu_detach (GTK_MENU (priv->menu));
1048 priv->menu = NULL;
1049 }
1050
1051 if (priv->popover)
1052 {
1053 g_signal_handlers_disconnect_by_func (priv->popover,
1054 menu_deactivate_cb,
1055 object);
1056 g_signal_handlers_disconnect_by_func (priv->popover,
1057 popover_destroy_cb,
1058 object);
1059 gtk_popover_set_relative_to (GTK_POPOVER (priv->popover), NULL);
1060 priv->popover = NULL;
1061 }
1062
1063 set_align_widget_pointer (GTK_MENU_BUTTON (object), NULL);
1064
1065 g_clear_object (&priv->model);
1066
1067 G_OBJECT_CLASS (gtk_menu_button_parent_class)->dispose (object);
1068 }
1069
1070 /**
1071 * gtk_menu_button_set_use_popover:
1072 * @menu_button: a #GtkMenuButton
1073 * @use_popover: %TRUE to construct a popover from the menu model
1074 *
1075 * Sets whether to construct a #GtkPopover instead of #GtkMenu
1076 * when gtk_menu_button_set_menu_model() is called. Note that
1077 * this property is only consulted when a new menu model is set.
1078 *
1079 * Since: 3.12
1080 */
1081 void
gtk_menu_button_set_use_popover(GtkMenuButton * menu_button,gboolean use_popover)1082 gtk_menu_button_set_use_popover (GtkMenuButton *menu_button,
1083 gboolean use_popover)
1084 {
1085 GtkMenuButtonPrivate *priv;
1086
1087 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1088
1089 priv = menu_button->priv;
1090
1091 use_popover = use_popover != FALSE;
1092
1093 if (priv->use_popover == use_popover)
1094 return;
1095
1096 priv->use_popover = use_popover;
1097
1098 g_object_freeze_notify (G_OBJECT (menu_button));
1099
1100 if (priv->model)
1101 gtk_menu_button_set_menu_model (menu_button, priv->model);
1102
1103 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_USE_POPOVER]);
1104
1105 g_object_thaw_notify (G_OBJECT (menu_button));
1106 }
1107
1108 /**
1109 * gtk_menu_button_get_use_popover:
1110 * @menu_button: a #GtkMenuButton
1111 *
1112 * Returns whether a #GtkPopover or a #GtkMenu will be constructed
1113 * from the menu model.
1114 *
1115 * Returns: %TRUE if using a #GtkPopover
1116 *
1117 * Since: 3.12
1118 */
1119 gboolean
gtk_menu_button_get_use_popover(GtkMenuButton * menu_button)1120 gtk_menu_button_get_use_popover (GtkMenuButton *menu_button)
1121 {
1122 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), FALSE);
1123
1124 return menu_button->priv->use_popover;
1125 }
1126
1127 /**
1128 * gtk_menu_button_set_popover:
1129 * @menu_button: a #GtkMenuButton
1130 * @popover: (nullable): a #GtkPopover, or %NULL to unset and disable the button
1131 *
1132 * Sets the #GtkPopover that will be popped up when the @menu_button is clicked,
1133 * or %NULL to dissociate any existing popover and disable the button.
1134 *
1135 * If #GtkMenuButton:menu-model or #GtkMenuButton:popup are set, those objects
1136 * are dissociated from the @menu_button, and those properties are set to %NULL.
1137 *
1138 * Since: 3.12
1139 */
1140 void
gtk_menu_button_set_popover(GtkMenuButton * menu_button,GtkWidget * popover)1141 gtk_menu_button_set_popover (GtkMenuButton *menu_button,
1142 GtkWidget *popover)
1143 {
1144 GtkMenuButtonPrivate *priv = menu_button->priv;
1145
1146 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1147 g_return_if_fail (GTK_IS_POPOVER (popover) || popover == NULL);
1148
1149 g_object_freeze_notify (G_OBJECT (menu_button));
1150
1151 g_clear_object (&priv->model);
1152
1153 if (priv->popover)
1154 {
1155 if (gtk_widget_get_visible (priv->popover))
1156 gtk_widget_hide (priv->popover);
1157
1158 g_signal_handlers_disconnect_by_func (priv->popover,
1159 menu_deactivate_cb,
1160 menu_button);
1161 g_signal_handlers_disconnect_by_func (priv->popover,
1162 popover_destroy_cb,
1163 menu_button);
1164
1165 gtk_popover_set_relative_to (GTK_POPOVER (priv->popover), NULL);
1166 }
1167
1168 priv->popover = popover;
1169
1170 if (popover)
1171 {
1172 gtk_popover_set_relative_to (GTK_POPOVER (priv->popover), GTK_WIDGET (menu_button));
1173 g_signal_connect_swapped (priv->popover, "closed",
1174 G_CALLBACK (menu_deactivate_cb), menu_button);
1175 g_signal_connect_swapped (priv->popover, "destroy",
1176 G_CALLBACK (popover_destroy_cb), menu_button);
1177 update_popover_direction (menu_button);
1178 gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (menu_button)), "menu-button");
1179 }
1180
1181 if (popover && priv->menu)
1182 gtk_menu_button_set_popup (menu_button, NULL);
1183
1184 update_sensitivity (menu_button);
1185
1186 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_POPOVER]);
1187 g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_MENU_MODEL]);
1188 g_object_thaw_notify (G_OBJECT (menu_button));
1189 }
1190
1191 /**
1192 * gtk_menu_button_get_popover:
1193 * @menu_button: a #GtkMenuButton
1194 *
1195 * Returns the #GtkPopover that pops out of the button.
1196 * If the button is not using a #GtkPopover, this function
1197 * returns %NULL.
1198 *
1199 * Returns: (nullable) (transfer none): a #GtkPopover or %NULL
1200 *
1201 * Since: 3.12
1202 */
1203 GtkPopover *
gtk_menu_button_get_popover(GtkMenuButton * menu_button)1204 gtk_menu_button_get_popover (GtkMenuButton *menu_button)
1205 {
1206 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
1207
1208 return GTK_POPOVER (menu_button->priv->popover);
1209 }
1210