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