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 * GtkMenuButton:
23 *
24 * The `GtkMenuButton` widget is used to display a popup when clicked.
25 *
26 * ![An example GtkMenuButton](menu-button.png)
27 *
28 * This popup can be provided either as a `GtkPopover` or as an abstract
29 * `GMenuModel`.
30 *
31 * The `GtkMenuButton` widget can show either an icon (set with the
32 * [property@Gtk.MenuButton:icon-name] property) or a label (set with the
33 * [property@Gtk.MenuButton:label] property). If neither is explicitly set,
34 * a [class@Gtk.Image] is automatically created, using an arrow image oriented
35 * according to [property@Gtk.MenuButton:direction] or the generic
36 * “open-menu-symbolic” icon if the direction is not set.
37 *
38 * The positioning of the popup is determined by the
39 * [property@Gtk.MenuButton:direction] property of the menu button.
40 *
41 * For menus, the [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign]
42 * properties of the menu are also taken into account. For example, when the
43 * direction is %GTK_ARROW_DOWN and the horizontal alignment is %GTK_ALIGN_START,
44 * the menu will be positioned below the button, with the starting edge
45 * (depending on the text direction) of the menu aligned with the starting
46 * edge of the button. If there is not enough space below the button, the
47 * menu is popped up above the button instead. If the alignment would move
48 * part of the menu offscreen, it is “pushed in”.
49 *
50 * | | start | center | end |
51 * | - | --- | --- | --- |
52 * | **down** | ![](down-start.png) | ![](down-center.png) | ![](down-end.png) |
53 * | **up** | ![](up-start.png) | ![](up-center.png) | ![](up-end.png) |
54 * | **left** | ![](left-start.png) | ![](left-center.png) | ![](left-end.png) |
55 * | **right** | ![](right-start.png) | ![](right-center.png) | ![](right-end.png) |
56 *
57 * # CSS nodes
58 *
59 * ```
60 * menubutton
61 * ╰── button.toggle
62 * ╰── <content>
63 * ╰── [arrow]
64 * ```
65 *
66 * `GtkMenuButton` has a single CSS node with name `menubutton`
67 * which contains a `button` node with a `.toggle` style class.
68 *
69 * If the button contains an icon, it will have the `.image-button` style class,
70 * if it contains text, it will have `.text-button` style class. If an arrow is
71 * visible in addition to an icon, text or a custom child, it will also have
72 * `.arrow-button` style class.
73 *
74 * Inside the toggle button content, there is an `arrow` node for
75 * the indicator, which will carry one of the `.none`, `.up`, `.down`,
76 * `.left` or `.right` style classes to indicate the direction that
77 * the menu will appear in. The CSS is expected to provide a suitable
78 * image for each of these cases using the `-gtk-icon-source` property.
79 *
80 * Optionally, the `menubutton` node can carry the `.circular` style class
81 * to request a round appearance.
82 *
83 * # Accessibility
84 *
85 * `GtkMenuButton` uses the %GTK_ACCESSIBLE_ROLE_BUTTON role.
86 */
87
88#include "config.h"
89
90#include "gtkactionable.h"
91#include "gtkbuildable.h"
92#include "gtkbuiltiniconprivate.h"
93#include "gtkintl.h"
94#include "gtkimage.h"
95#include "gtkmain.h"
96#include "gtkmenubutton.h"
97#include "gtkmenubuttonprivate.h"
98#include "gtkpopover.h"
99#include "gtkpopovermenu.h"
100#include "gtkprivate.h"
101#include "gtktypebuiltins.h"
102#include "gtklabel.h"
103#include "gtkbox.h"
104#include "gtkwidgetprivate.h"
105#include "gtkbuttonprivate.h"
106#include "gtknative.h"
107#include "gtkwindow.h"
108
109typedef struct _GtkMenuButtonClass GtkMenuButtonClass;
110typedef struct _GtkMenuButtonPrivate GtkMenuButtonPrivate;
111
112struct _GtkMenuButton
113{
114 GtkWidget parent_instance;
115
116 GtkWidget *button;
117 GtkWidget *popover; /* Only one at a time can be set */
118 GMenuModel *model;
119
120 GtkMenuButtonCreatePopupFunc create_popup_func;
121 gpointer create_popup_user_data;
122 GDestroyNotify create_popup_destroy_notify;
123
124 GtkWidget *label_widget;
125 GtkWidget *image_widget;
126 GtkWidget *arrow_widget;
127 GtkWidget *child;
128 GtkArrowType arrow_type;
129 gboolean always_show_arrow;
130
131 gboolean primary;
132};
133
134struct _GtkMenuButtonClass
135{
136 GtkWidgetClass parent_class;
137
138 void (* activate) (GtkMenuButton *self);
139};
140
141enum
142{
143 PROP_0,
144 PROP_MENU_MODEL,
145 PROP_DIRECTION,
146 PROP_POPOVER,
147 PROP_ICON_NAME,
148 PROP_ALWAYS_SHOW_ARROW,
149 PROP_LABEL,
150 PROP_USE_UNDERLINE,
151 PROP_HAS_FRAME,
152 PROP_PRIMARY,
153 PROP_CHILD,
154 LAST_PROP
155};
156
157enum {
158 ACTIVATE,
159 LAST_SIGNAL
160};
161
162static GParamSpec *menu_button_props[LAST_PROP];
163static guint signals[LAST_SIGNAL] = { 0 };
164
165static void gtk_menu_button_buildable_iface_init (GtkBuildableIface *iface);
166
167G_DEFINE_TYPE_WITH_CODE (GtkMenuButton, gtk_menu_button, GTK_TYPE_WIDGET,
168 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_menu_button_buildable_iface_init))
169
170static void gtk_menu_button_dispose (GObject *object);
171
172static void
173gtk_menu_button_set_property (GObject *object,
174 guint property_id,
175 const GValue *value,
176 GParamSpec *pspec)
177{
178 GtkMenuButton *self = GTK_MENU_BUTTON (object);
179
180 switch (property_id)
181 {
182 case PROP_MENU_MODEL:
183 gtk_menu_button_set_menu_model (menu_button: self, menu_model: g_value_get_object (value));
184 break;
185 case PROP_DIRECTION:
186 gtk_menu_button_set_direction (menu_button: self, direction: g_value_get_enum (value));
187 break;
188 case PROP_POPOVER:
189 gtk_menu_button_set_popover (menu_button: self, popover: g_value_get_object (value));
190 break;
191 case PROP_ICON_NAME:
192 gtk_menu_button_set_icon_name (menu_button: self, icon_name: g_value_get_string (value));
193 break;
194 case PROP_ALWAYS_SHOW_ARROW:
195 gtk_menu_button_set_always_show_arrow (menu_button: self, always_show_arrow: g_value_get_boolean (value));
196 break;
197 case PROP_LABEL:
198 gtk_menu_button_set_label (menu_button: self, label: g_value_get_string (value));
199 break;
200 case PROP_USE_UNDERLINE:
201 gtk_menu_button_set_use_underline (menu_button: self, use_underline: g_value_get_boolean (value));
202 break;
203 case PROP_HAS_FRAME:
204 gtk_menu_button_set_has_frame (menu_button: self, has_frame: g_value_get_boolean (value));
205 break;
206 case PROP_PRIMARY:
207 gtk_menu_button_set_primary (menu_button: self, primary: g_value_get_boolean (value));
208 break;
209 case PROP_CHILD:
210 gtk_menu_button_set_child (menu_button: self, child: g_value_get_object (value));
211 break;
212 default:
213 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
214 }
215}
216
217static void
218gtk_menu_button_get_property (GObject *object,
219 guint property_id,
220 GValue *value,
221 GParamSpec *pspec)
222{
223 GtkMenuButton *self = GTK_MENU_BUTTON (object);
224
225 switch (property_id)
226 {
227 case PROP_MENU_MODEL:
228 g_value_set_object (value, v_object: self->model);
229 break;
230 case PROP_DIRECTION:
231 g_value_set_enum (value, v_enum: self->arrow_type);
232 break;
233 case PROP_POPOVER:
234 g_value_set_object (value, v_object: self->popover);
235 break;
236 case PROP_ICON_NAME:
237 g_value_set_string (value, v_string: gtk_menu_button_get_icon_name (GTK_MENU_BUTTON (object)));
238 break;
239 case PROP_ALWAYS_SHOW_ARROW:
240 g_value_set_boolean (value, v_boolean: gtk_menu_button_get_always_show_arrow (menu_button: self));
241 break;
242 case PROP_LABEL:
243 g_value_set_string (value, v_string: gtk_menu_button_get_label (GTK_MENU_BUTTON (object)));
244 break;
245 case PROP_USE_UNDERLINE:
246 g_value_set_boolean (value, v_boolean: gtk_menu_button_get_use_underline (GTK_MENU_BUTTON (object)));
247 break;
248 case PROP_HAS_FRAME:
249 g_value_set_boolean (value, v_boolean: gtk_menu_button_get_has_frame (GTK_MENU_BUTTON (object)));
250 break;
251 case PROP_PRIMARY:
252 g_value_set_boolean (value, v_boolean: gtk_menu_button_get_primary (GTK_MENU_BUTTON (object)));
253 break;
254 case PROP_CHILD:
255 g_value_set_object (value, v_object: gtk_menu_button_get_child (menu_button: self));
256 break;
257 default:
258 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
259 }
260}
261
262static void
263gtk_menu_button_notify (GObject *object,
264 GParamSpec *pspec)
265{
266 if (strcmp (s1: pspec->name, s2: "focus-on-click") == 0)
267 {
268 GtkMenuButton *self = GTK_MENU_BUTTON (object);
269
270 gtk_widget_set_focus_on_click (widget: self->button,
271 focus_on_click: gtk_widget_get_focus_on_click (GTK_WIDGET (self)));
272 }
273
274 if (G_OBJECT_CLASS (gtk_menu_button_parent_class)->notify)
275 G_OBJECT_CLASS (gtk_menu_button_parent_class)->notify (object, pspec);
276}
277
278static void
279gtk_menu_button_state_flags_changed (GtkWidget *widget,
280 GtkStateFlags previous_state_flags)
281{
282 GtkMenuButton *self = GTK_MENU_BUTTON (widget);
283
284 if (!gtk_widget_is_sensitive (widget))
285 {
286 if (self->popover)
287 gtk_widget_hide (widget: self->popover);
288 }
289}
290
291static void
292gtk_menu_button_toggled (GtkMenuButton *self)
293{
294 const gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->button));
295
296 /* Might set a new menu/popover */
297 if (active && self->create_popup_func)
298 {
299 self->create_popup_func (self, self->create_popup_user_data);
300 }
301
302 if (self->popover)
303 {
304 if (active)
305 {
306 gtk_popover_popup (GTK_POPOVER (self->popover));
307 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: self),
308 first_state: GTK_ACCESSIBLE_STATE_EXPANDED, TRUE,
309 -1);
310 }
311 else
312 {
313 gtk_popover_popdown (GTK_POPOVER (self->popover));
314 gtk_accessible_reset_state (self: GTK_ACCESSIBLE (ptr: self),
315 state: GTK_ACCESSIBLE_STATE_EXPANDED);
316 }
317 }
318}
319
320static void
321gtk_menu_button_measure (GtkWidget *widget,
322 GtkOrientation orientation,
323 int for_size,
324 int *minimum,
325 int *natural,
326 int *minimum_baseline,
327 int *natural_baseline)
328{
329 GtkMenuButton *self = GTK_MENU_BUTTON (widget);
330
331 gtk_widget_measure (widget: self->button,
332 orientation,
333 for_size,
334 minimum, natural,
335 minimum_baseline, natural_baseline);
336
337}
338
339static void
340gtk_menu_button_size_allocate (GtkWidget *widget,
341 int width,
342 int height,
343 int baseline)
344{
345 GtkMenuButton *self= GTK_MENU_BUTTON (widget);
346
347 gtk_widget_size_allocate (widget: self->button,
348 allocation: &(GtkAllocation) { 0, 0, width, height },
349 baseline);
350 if (self->popover)
351 gtk_popover_present (GTK_POPOVER (self->popover));
352}
353
354static gboolean
355gtk_menu_button_focus (GtkWidget *widget,
356 GtkDirectionType direction)
357{
358 GtkMenuButton *self = GTK_MENU_BUTTON (widget);
359
360 if (self->popover && gtk_widget_get_visible (widget: self->popover))
361 return gtk_widget_child_focus (widget: self->popover, direction);
362 else
363 return gtk_widget_child_focus (widget: self->button, direction);
364}
365
366static gboolean
367gtk_menu_button_grab_focus (GtkWidget *widget)
368{
369 GtkMenuButton *self = GTK_MENU_BUTTON (widget);
370
371 return gtk_widget_grab_focus (widget: self->button);
372}
373
374static void
375gtk_menu_button_activate (GtkMenuButton *self)
376{
377 gtk_widget_activate (widget: self->button);
378}
379
380static void gtk_menu_button_root (GtkWidget *widget);
381static void gtk_menu_button_unroot (GtkWidget *widget);
382
383static void
384gtk_menu_button_class_init (GtkMenuButtonClass *klass)
385{
386 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
387 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
388
389 gobject_class->set_property = gtk_menu_button_set_property;
390 gobject_class->get_property = gtk_menu_button_get_property;
391 gobject_class->notify = gtk_menu_button_notify;
392 gobject_class->dispose = gtk_menu_button_dispose;
393
394 widget_class->root = gtk_menu_button_root;
395 widget_class->unroot = gtk_menu_button_unroot;
396 widget_class->measure = gtk_menu_button_measure;
397 widget_class->size_allocate = gtk_menu_button_size_allocate;
398 widget_class->state_flags_changed = gtk_menu_button_state_flags_changed;
399 widget_class->focus = gtk_menu_button_focus;
400 widget_class->grab_focus = gtk_menu_button_grab_focus;
401
402 klass->activate = gtk_menu_button_activate;
403
404 /**
405 * GtkMenuButton:menu-model: (attributes org.gtk.Property.get=gtk_menu_button_get_menu_model org.gtk.Property.set=gtk_menu_button_set_menu_model)
406 *
407 * The `GMenuModel` from which the popup will be created.
408 *
409 * See [method@Gtk.MenuButton.set_menu_model] for the interaction
410 * with the [property@Gtk.MenuButton:popover] property.
411 */
412 menu_button_props[PROP_MENU_MODEL] =
413 g_param_spec_object (name: "menu-model",
414 P_("Menu model"),
415 P_("The model from which the popup is made."),
416 G_TYPE_MENU_MODEL,
417 GTK_PARAM_READWRITE);
418
419 /**
420 * GtkMenuButton:direction: (attributes org.gtk.Property.get=gtk_menu_button_get_direction org.gtk.Property.set=gtk_menu_button_set_direction)
421 *
422 * The `GtkArrowType` representing the direction in which the
423 * menu or popover will be popped out.
424 */
425 menu_button_props[PROP_DIRECTION] =
426 g_param_spec_enum (name: "direction",
427 P_("Direction"),
428 P_("The direction the arrow should point."),
429 enum_type: GTK_TYPE_ARROW_TYPE,
430 default_value: GTK_ARROW_DOWN,
431 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
432
433 /**
434 * GtkMenuButton:popover: (attributes org.gtk.Property.get=gtk_menu_button_get_popover org.gtk.Property.set=gtk_menu_button_set_popover)
435 *
436 * The `GtkPopover` that will be popped up when the button is clicked.
437 */
438 menu_button_props[PROP_POPOVER] =
439 g_param_spec_object (name: "popover",
440 P_("Popover"),
441 P_("The popover"),
442 GTK_TYPE_POPOVER,
443 flags: G_PARAM_READWRITE);
444
445 /**
446 * GtkMenuButton:icon-name: (attributes org.gtk.Property.get=gtk_menu_button_get_icon_name org.gtk.Property.set=gtk_menu_button_set_icon_name)
447 *
448 * The name of the icon used to automatically populate the button.
449 */
450 menu_button_props[PROP_ICON_NAME] =
451 g_param_spec_string (name: "icon-name",
452 P_("Icon Name"),
453 P_("The name of the icon used to automatically populate the button"),
454 NULL,
455 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
456
457 /**
458 * GtkMenuButton:always-show-arrow: (attributes org.gtk.Property.get=gtk_menu_button_get_always_show_arrow org.gtk.Property.set=gtk_menu_button_set_always_show_arrow)
459 *
460 * Whether to show a dropdown arrow even when using an icon or a custom child.
461 *
462 * Since: 4.4
463 */
464 menu_button_props[PROP_ALWAYS_SHOW_ARROW] =
465 g_param_spec_boolean (name: "always-show-arrow",
466 P_("Always Show Arrow"),
467 P_("Whether to show a dropdown arrow even when using an icon or a custom child"),
468 FALSE,
469 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
470
471 /**
472 * GtkMenuButton:label: (attributes org.gtk.Property.get=gtk_menu_button_get_label org.gtk.Property.set=gtk_menu_button_set_label)
473 *
474 * The label for the button.
475 */
476 menu_button_props[PROP_LABEL] =
477 g_param_spec_string (name: "label",
478 P_("Label"),
479 P_("The label for the button"),
480 NULL,
481 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
482
483 /**
484 * GtkMenuButton:use-underline: (attributes org.gtk.Property.get=gtk_menu_button_get_use_underline org.gtk.Property.set=gtk_menu_button_set_use_underline)
485 *
486 * If set an underscore in the text indicates a mnemonic.
487 */
488 menu_button_props[PROP_USE_UNDERLINE] =
489 g_param_spec_boolean (name: "use-underline",
490 P_("Use underline"),
491 P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
492 FALSE,
493 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
494
495 /**
496 * GtkMenuButton:has-frame: (attributes org.gtk.Property.get=gtk_menu_button_get_has_frame org.gtk.Property.set=gtk_menu_button_set_has_frame)
497 *
498 * Whether the button has a frame.
499 */
500 menu_button_props[PROP_HAS_FRAME] =
501 g_param_spec_boolean (name: "has-frame",
502 P_("Has frame"),
503 P_("Whether the button has a frame"),
504 TRUE,
505 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
506
507 /**
508 * GtkMenuButton:primary: (attributes org.gtk.Property.get=gtk_menu_button_get_primary org.gtk.Property.set=gtk_menu_button_set_primary)
509 *
510 * Whether the menu button acts as a primary menu.
511 *
512 * Primary menus can be opened using the <kbd>F10</kbd> key
513 *
514 * Since: 4.4
515 */
516 menu_button_props[PROP_PRIMARY] =
517 g_param_spec_boolean (name: "primary",
518 P_("Primary"),
519 P_("Whether the menubutton acts as a primary menu"),
520 FALSE,
521 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
522
523 /**
524 * GtkMenuButton:child: (attributes org.gtk.Property.get=gtk_menu_button_get_child org.gtk.Property.set=gtk_menu_button_set_child)
525 *
526 * The child widget.
527 *
528 * Since: 4.6
529 */
530 menu_button_props[PROP_CHILD] =
531 g_param_spec_object (name: "child",
532 P_("Child"),
533 P_("The child widget"),
534 GTK_TYPE_WIDGET,
535 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
536
537 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: menu_button_props);
538
539 /**
540 * GtkMenuButton::activate:
541 * @widget: the object which received the signal.
542 *
543 * Emitted to when the menu button is activated.
544 *
545 * The `::activate` signal on `GtkMenuButton` is an action signal and
546 * emitting it causes the button to pop up its menu.
547 *
548 * Since: 4.4
549 */
550 signals[ACTIVATE] =
551 g_signal_new (I_ ("activate"),
552 G_OBJECT_CLASS_TYPE (gobject_class),
553 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
554 G_STRUCT_OFFSET (GtkMenuButtonClass, activate),
555 NULL, NULL,
556 NULL,
557 G_TYPE_NONE, n_params: 0);
558
559 gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE]);
560
561 gtk_widget_class_set_css_name (widget_class, I_("menubutton"));
562 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_BUTTON);
563}
564
565static void
566set_arrow_type (GtkWidget *arrow,
567 GtkArrowType arrow_type,
568 gboolean visible)
569{
570 gtk_widget_remove_css_class (widget: arrow, css_class: "none");
571 gtk_widget_remove_css_class (widget: arrow, css_class: "down");
572 gtk_widget_remove_css_class (widget: arrow, css_class: "up");
573 gtk_widget_remove_css_class (widget: arrow, css_class: "left");
574 gtk_widget_remove_css_class (widget: arrow, css_class: "right");
575 switch (arrow_type)
576 {
577 case GTK_ARROW_NONE:
578 gtk_widget_add_css_class (widget: arrow, css_class: "none");
579 break;
580 case GTK_ARROW_DOWN:
581 gtk_widget_add_css_class (widget: arrow, css_class: "down");
582 break;
583 case GTK_ARROW_UP:
584 gtk_widget_add_css_class (widget: arrow, css_class: "up");
585 break;
586 case GTK_ARROW_LEFT:
587 gtk_widget_add_css_class (widget: arrow, css_class: "left");
588 break;
589 case GTK_ARROW_RIGHT:
590 gtk_widget_add_css_class (widget: arrow, css_class: "right");
591 break;
592 default:
593 break;
594 }
595
596 if (visible)
597 gtk_widget_show (widget: arrow);
598 else
599 gtk_widget_hide (widget: arrow);
600}
601
602static void
603update_style_classes (GtkMenuButton *menu_button)
604{
605 gboolean has_icon = menu_button->image_widget != NULL;
606 gboolean has_label = menu_button->label_widget != NULL;
607 gboolean has_only_arrow = menu_button->arrow_widget == gtk_button_get_child (GTK_BUTTON (menu_button->button));
608 gboolean has_arrow = gtk_widget_get_visible (widget: menu_button->arrow_widget);
609
610 if (has_only_arrow || has_icon)
611 gtk_widget_add_css_class (widget: menu_button->button, css_class: "image-button");
612 else
613 gtk_widget_remove_css_class (widget: menu_button->button, css_class: "image-button");
614
615 if (has_label)
616 gtk_widget_add_css_class (widget: menu_button->button, css_class: "text-button");
617 else
618 gtk_widget_remove_css_class (widget: menu_button->button, css_class: "text-button");
619
620 if (has_arrow && !has_only_arrow)
621 gtk_widget_add_css_class (widget: menu_button->button, css_class: "arrow-button");
622 else
623 gtk_widget_remove_css_class (widget: menu_button->button, css_class: "arrow-button");
624}
625
626static void
627update_arrow (GtkMenuButton *menu_button)
628{
629 gboolean has_only_arrow, is_text_button;
630
631 if (menu_button->arrow_widget == NULL)
632 return;
633
634 has_only_arrow = menu_button->arrow_widget == gtk_button_get_child (GTK_BUTTON (menu_button->button));
635 is_text_button = menu_button->label_widget != NULL;
636
637 set_arrow_type (arrow: menu_button->arrow_widget,
638 arrow_type: menu_button->arrow_type,
639 visible: has_only_arrow ||
640 ((is_text_button || menu_button->always_show_arrow) &&
641 (menu_button->arrow_type != GTK_ARROW_NONE)));
642
643 update_style_classes (menu_button);
644}
645
646static void
647add_arrow (GtkMenuButton *self)
648{
649 GtkWidget *arrow;
650
651 arrow = gtk_builtin_icon_new (css_name: "arrow");
652 gtk_widget_set_halign (widget: arrow, align: GTK_ALIGN_CENTER);
653 set_arrow_type (arrow, arrow_type: self->arrow_type, TRUE);
654 gtk_button_set_child (GTK_BUTTON (self->button), child: arrow);
655 self->arrow_widget = arrow;
656}
657
658static void
659gtk_menu_button_init (GtkMenuButton *self)
660{
661 self->arrow_type = GTK_ARROW_DOWN;
662
663 self->button = gtk_toggle_button_new ();
664 gtk_widget_set_parent (widget: self->button, GTK_WIDGET (self));
665 g_signal_connect_swapped (self->button, "toggled", G_CALLBACK (gtk_menu_button_toggled), self);
666 add_arrow (self);
667 update_style_classes (menu_button: self);
668
669 gtk_widget_set_sensitive (widget: self->button, FALSE);
670
671 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "popup");
672
673 gtk_accessible_update_relation (self: GTK_ACCESSIBLE (ptr: self->button),
674 first_relation: GTK_ACCESSIBLE_RELATION_LABELLED_BY, self, NULL,
675 GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, self, NULL,
676 -1);
677}
678
679static GtkBuildableIface *parent_buildable_iface;
680
681static void
682gtk_menu_button_buildable_add_child (GtkBuildable *buildable,
683 GtkBuilder *builder,
684 GObject *child,
685 const char *type)
686{
687 if (GTK_IS_WIDGET (child))
688 gtk_menu_button_set_child (GTK_MENU_BUTTON (buildable), GTK_WIDGET (child));
689 else
690 parent_buildable_iface->add_child (buildable, builder, child, type);
691}
692
693static void
694gtk_menu_button_buildable_iface_init (GtkBuildableIface *iface)
695{
696 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
697
698 iface->add_child = gtk_menu_button_buildable_add_child;
699}
700
701/**
702 * gtk_menu_button_new:
703 *
704 * Creates a new `GtkMenuButton` widget with downwards-pointing
705 * arrow as the only child.
706 *
707 * You can replace the child widget with another `GtkWidget`
708 * should you wish to.
709 *
710 * Returns: The newly created `GtkMenuButton`
711 */
712GtkWidget *
713gtk_menu_button_new (void)
714{
715 return g_object_new (GTK_TYPE_MENU_BUTTON, NULL);
716}
717
718static void
719update_sensitivity (GtkMenuButton *self)
720{
721 gboolean has_popup;
722
723 has_popup = self->popover != NULL || self->create_popup_func != NULL;
724
725 gtk_widget_set_sensitive (widget: self->button, sensitive: has_popup);
726
727 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
728 first_property: GTK_ACCESSIBLE_PROPERTY_HAS_POPUP, has_popup,
729 -1);
730 if (self->popover != NULL)
731 gtk_accessible_update_relation (self: GTK_ACCESSIBLE (ptr: self),
732 first_relation: GTK_ACCESSIBLE_RELATION_CONTROLS, self->popover, NULL,
733 -1);
734 else
735 gtk_accessible_reset_relation (self: GTK_ACCESSIBLE (ptr: self),
736 relation: GTK_ACCESSIBLE_RELATION_CONTROLS);
737}
738
739static gboolean
740menu_deactivate_cb (GtkMenuButton *self)
741{
742 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE);
743
744 return TRUE;
745}
746
747/**
748 * gtk_menu_button_set_menu_model: (attributes org.gtk.Method.set_property=menu-model)
749 * @menu_button: a `GtkMenuButton`
750 * @menu_model: (nullable): a `GMenuModel`, or %NULL to unset and disable the
751 * button
752 *
753 * Sets the `GMenuModel` from which the popup will be constructed.
754 *
755 * If @menu_model is %NULL, the button is disabled.
756 *
757 * A [class@Gtk.Popover] will be created from the menu model with
758 * [ctor@Gtk.PopoverMenu.new_from_model]. Actions will be connected
759 * as documented for this function.
760 *
761 * If [property@Gtk.MenuButton:popover] is already set, it will be
762 * dissociated from the @menu_button, and the property is set to %NULL.
763 */
764void
765gtk_menu_button_set_menu_model (GtkMenuButton *menu_button,
766 GMenuModel *menu_model)
767{
768 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
769 g_return_if_fail (G_IS_MENU_MODEL (menu_model) || menu_model == NULL);
770
771 g_object_freeze_notify (G_OBJECT (menu_button));
772
773 if (menu_model)
774 g_object_ref (menu_model);
775
776 if (menu_model)
777 {
778 GtkWidget *popover;
779
780 popover = gtk_popover_menu_new_from_model (model: menu_model);
781 gtk_menu_button_set_popover (menu_button, popover);
782 }
783 else
784 {
785 gtk_menu_button_set_popover (menu_button, NULL);
786 }
787
788 menu_button->model = menu_model;
789 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_MENU_MODEL]);
790
791 g_object_thaw_notify (G_OBJECT (menu_button));
792}
793
794/**
795 * gtk_menu_button_get_menu_model: (attributes org.gtk.Method.get_property=menu-model)
796 * @menu_button: a `GtkMenuButton`
797 *
798 * Returns the `GMenuModel` used to generate the popup.
799 *
800 * Returns: (nullable) (transfer none): a `GMenuModel`
801 */
802GMenuModel *
803gtk_menu_button_get_menu_model (GtkMenuButton *menu_button)
804{
805 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
806
807 return menu_button->model;
808}
809
810static void
811update_popover_direction (GtkMenuButton *self)
812{
813 if (!self->popover)
814 return;
815
816 switch (self->arrow_type)
817 {
818 case GTK_ARROW_UP:
819 gtk_popover_set_position (GTK_POPOVER (self->popover), position: GTK_POS_TOP);
820 break;
821 case GTK_ARROW_DOWN:
822 case GTK_ARROW_NONE:
823 gtk_popover_set_position (GTK_POPOVER (self->popover), position: GTK_POS_BOTTOM);
824 break;
825 case GTK_ARROW_LEFT:
826 gtk_popover_set_position (GTK_POPOVER (self->popover), position: GTK_POS_LEFT);
827 break;
828 case GTK_ARROW_RIGHT:
829 gtk_popover_set_position (GTK_POPOVER (self->popover), position: GTK_POS_RIGHT);
830 break;
831 default:
832 break;
833 }
834}
835
836static void
837popover_destroy_cb (GtkMenuButton *menu_button)
838{
839 gtk_menu_button_set_popover (menu_button, NULL);
840}
841
842/**
843 * gtk_menu_button_set_direction: (attributes org.gtk.Method.set_property=direction)
844 * @menu_button: a `GtkMenuButton`
845 * @direction: a `GtkArrowType`
846 *
847 * Sets the direction in which the popup will be popped up.
848 *
849 * If the button is automatically populated with an arrow icon,
850 * its direction will be changed to match.
851 *
852 * If the does not fit in the available space in the given direction,
853 * GTK will its best to keep it inside the screen and fully visible.
854 *
855 * If you pass %GTK_ARROW_NONE for a @direction, the popup will behave
856 * as if you passed %GTK_ARROW_DOWN (although you won’t see any arrows).
857 */
858void
859gtk_menu_button_set_direction (GtkMenuButton *menu_button,
860 GtkArrowType direction)
861{
862 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
863
864 if (menu_button->arrow_type == direction)
865 return;
866
867 menu_button->arrow_type = direction;
868 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_DIRECTION]);
869
870 update_arrow (menu_button);
871 update_popover_direction (self: menu_button);
872}
873
874/**
875 * gtk_menu_button_get_direction: (attributes org.gtk.Method.get_property=direction)
876 * @menu_button: a `GtkMenuButton`
877 *
878 * Returns the direction the popup will be pointing at when popped up.
879 *
880 * Returns: a `GtkArrowType` value
881 */
882GtkArrowType
883gtk_menu_button_get_direction (GtkMenuButton *menu_button)
884{
885 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), GTK_ARROW_DOWN);
886
887 return menu_button->arrow_type;
888}
889
890static void
891gtk_menu_button_dispose (GObject *object)
892{
893 GtkMenuButton *self = GTK_MENU_BUTTON (object);
894
895 if (self->popover)
896 {
897 g_signal_handlers_disconnect_by_func (self->popover,
898 menu_deactivate_cb,
899 object);
900 g_signal_handlers_disconnect_by_func (self->popover,
901 popover_destroy_cb,
902 object);
903 gtk_widget_unparent (widget: self->popover);
904 self->popover = NULL;
905 }
906
907 g_clear_object (&self->model);
908 g_clear_pointer (&self->button, gtk_widget_unparent);
909
910 if (self->create_popup_destroy_notify)
911 self->create_popup_destroy_notify (self->create_popup_user_data);
912
913 G_OBJECT_CLASS (gtk_menu_button_parent_class)->dispose (object);
914}
915
916/**
917 * gtk_menu_button_set_popover: (attributes org.gtk.Method.set_property=popover)
918 * @menu_button: a `GtkMenuButton`
919 * @popover: (nullable): a `GtkPopover`, or %NULL to unset and disable the button
920 *
921 * Sets the `GtkPopover` that will be popped up when the @menu_button is clicked.
922 *
923 * If @popover is %NULL, the button is disabled.
924 *
925 * If [property@Gtk.MenuButton:menu-model] is set, the menu model is dissociated
926 * from the @menu_button, and the property is set to %NULL.
927 */
928void
929gtk_menu_button_set_popover (GtkMenuButton *menu_button,
930 GtkWidget *popover)
931{
932 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
933 g_return_if_fail (GTK_IS_POPOVER (popover) || popover == NULL);
934
935 g_object_freeze_notify (G_OBJECT (menu_button));
936
937 g_clear_object (&menu_button->model);
938
939 if (menu_button->popover)
940 {
941 if (gtk_widget_get_visible (widget: menu_button->popover))
942 gtk_widget_hide (widget: menu_button->popover);
943
944 g_signal_handlers_disconnect_by_func (menu_button->popover,
945 menu_deactivate_cb,
946 menu_button);
947 g_signal_handlers_disconnect_by_func (menu_button->popover,
948 popover_destroy_cb,
949 menu_button);
950
951 gtk_widget_unparent (widget: menu_button->popover);
952 }
953
954 menu_button->popover = popover;
955
956 if (popover)
957 {
958 gtk_widget_set_parent (widget: menu_button->popover, GTK_WIDGET (menu_button));
959 g_signal_connect_swapped (menu_button->popover, "closed",
960 G_CALLBACK (menu_deactivate_cb), menu_button);
961 g_signal_connect_swapped (menu_button->popover, "destroy",
962 G_CALLBACK (popover_destroy_cb), menu_button);
963 update_popover_direction (self: menu_button);
964 }
965
966 update_sensitivity (self: menu_button);
967
968 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_POPOVER]);
969 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_MENU_MODEL]);
970 g_object_thaw_notify (G_OBJECT (menu_button));
971}
972
973/**
974 * gtk_menu_button_get_popover: (attributes org.gtk.Method.get_property=popover)
975 * @menu_button: a `GtkMenuButton`
976 *
977 * Returns the `GtkPopover` that pops out of the button.
978 *
979 * If the button is not using a `GtkPopover`, this function
980 * returns %NULL.
981 *
982 * Returns: (nullable) (transfer none): a `GtkPopover` or %NULL
983 */
984GtkPopover *
985gtk_menu_button_get_popover (GtkMenuButton *menu_button)
986{
987 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
988
989 return GTK_POPOVER (menu_button->popover);
990}
991
992/**
993 * gtk_menu_button_set_icon_name: (attributes org.gtk.Method.set_property=icon-name)
994 * @menu_button: a `GtkMenuButton`
995 * @icon_name: the icon name
996 *
997 * Sets the name of an icon to show inside the menu button.
998 *
999 * Setting icon name resets [property@Gtk.MenuButton:label] and
1000 * [property@Gtk.MenuButton:child].
1001 *
1002 * If [property@Gtk.MenuButton:always-show-arrow] is set to `TRUE` and
1003 * [property@Gtk.MenuButton:direction] is not `GTK_ARROW_NONE`, a dropdown arrow
1004 * will be shown next to the icon.
1005 */
1006void
1007gtk_menu_button_set_icon_name (GtkMenuButton *menu_button,
1008 const char *icon_name)
1009{
1010 GtkWidget *box, *image_widget, *arrow;
1011
1012 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1013
1014 g_object_freeze_notify (G_OBJECT (menu_button));
1015
1016 if (gtk_menu_button_get_label (menu_button))
1017 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_LABEL]);
1018
1019 if (gtk_menu_button_get_child (menu_button))
1020 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_CHILD]);
1021
1022 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
1023 gtk_widget_set_halign (widget: box, align: GTK_ALIGN_CENTER);
1024
1025 /* Because we are setting only an icon, let the inner button be labelled by us
1026 * so the accessible label can be overridden from, for example, an UI file
1027 * using GtkMenuButton as a child of something.
1028 */
1029 gtk_accessible_update_relation (self: GTK_ACCESSIBLE (ptr: menu_button->button),
1030 first_relation: GTK_ACCESSIBLE_RELATION_LABELLED_BY, menu_button, NULL,
1031 GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, menu_button, NULL,
1032 -1);
1033
1034 image_widget = g_object_new (GTK_TYPE_IMAGE,
1035 first_property_name: "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION,
1036 "icon-name", icon_name,
1037 NULL);
1038 menu_button->image_widget = image_widget;
1039
1040 arrow = gtk_builtin_icon_new (css_name: "arrow");
1041 menu_button->arrow_widget = arrow;
1042
1043 gtk_box_append (GTK_BOX (box), child: image_widget);
1044 gtk_box_append (GTK_BOX (box), child: arrow);
1045 gtk_button_set_child (GTK_BUTTON (menu_button->button), child: box);
1046
1047 menu_button->label_widget = NULL;
1048 menu_button->child = NULL;
1049
1050 update_arrow (menu_button);
1051
1052 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_ICON_NAME]);
1053
1054 g_object_thaw_notify (G_OBJECT (menu_button));
1055}
1056
1057/**
1058 * gtk_menu_button_get_icon_name: (attributes org.gtk.Method.get_property=icon-name)
1059 * @menu_button: a `GtkMenuButton`
1060 *
1061 * Gets the name of the icon shown in the button.
1062 *
1063 * Returns: (nullable): the name of the icon shown in the button
1064 */
1065const char *
1066gtk_menu_button_get_icon_name (GtkMenuButton *menu_button)
1067{
1068 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
1069
1070 if (menu_button->image_widget)
1071 return gtk_image_get_icon_name (GTK_IMAGE (menu_button->image_widget));
1072
1073 return NULL;
1074}
1075
1076/**
1077 * gtk_menu_button_set_always_show_arrow: (attributes org.gtk.Method.set_property=always-show-arrow)
1078 * @menu_button: a `GtkMenuButton`
1079 * @always_show_arrow: hether to show a dropdown arrow even when using an icon
1080 *
1081 * Sets whether to show a dropdown arrow even when using an icon or a custom
1082 * child.
1083 *
1084 * Since: 4.4
1085 */
1086void
1087gtk_menu_button_set_always_show_arrow (GtkMenuButton *menu_button,
1088 gboolean always_show_arrow)
1089{
1090 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1091
1092 always_show_arrow = !!always_show_arrow;
1093
1094 if (always_show_arrow == menu_button->always_show_arrow)
1095 return;
1096
1097 menu_button->always_show_arrow = always_show_arrow;
1098
1099 update_arrow (menu_button);
1100
1101 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_ALWAYS_SHOW_ARROW]);
1102}
1103
1104/**
1105 * gtk_menu_button_get_always_show_arrow: (attributes org.gtk.Method.get_property=always-show-arrow)
1106 * @menu_button: a `GtkMenuButton`
1107 *
1108 * Gets whether to show a dropdown arrow even when using an icon.
1109 *
1110 * Returns: whether to show a dropdown arrow even when using an icon
1111 *
1112 * Since: 4.4
1113 */
1114gboolean
1115gtk_menu_button_get_always_show_arrow (GtkMenuButton *menu_button)
1116{
1117 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), FALSE);
1118
1119 return menu_button->always_show_arrow;
1120}
1121
1122/**
1123 * gtk_menu_button_set_label: (attributes org.gtk.Method.set_property=label)
1124 * @menu_button: a `GtkMenuButton`
1125 * @label: the label
1126 *
1127 * Sets the label to show inside the menu button.
1128 *
1129 * Setting a label resets [property@Gtk.MenuButton:icon-name] and
1130 * [property@Gtk.MenuButton:child].
1131 *
1132 * If [property@Gtk.MenuButton:direction] is not `GTK_ARROW_NONE`, a dropdown
1133 * arrow will be shown next to the label.
1134 */
1135void
1136gtk_menu_button_set_label (GtkMenuButton *menu_button,
1137 const char *label)
1138{
1139 GtkWidget *box;
1140 GtkWidget *label_widget;
1141 GtkWidget *arrow;
1142
1143 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1144
1145 g_object_freeze_notify (G_OBJECT (menu_button));
1146
1147 if (gtk_menu_button_get_icon_name (menu_button))
1148 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_ICON_NAME]);
1149 if (gtk_menu_button_get_child (menu_button))
1150 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_CHILD]);
1151
1152 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
1153 label_widget = gtk_label_new (str: label);
1154 gtk_label_set_xalign (GTK_LABEL (label_widget), xalign: 0);
1155 gtk_label_set_use_underline (GTK_LABEL (label_widget),
1156 setting: gtk_button_get_use_underline (GTK_BUTTON (menu_button->button)));
1157 gtk_widget_set_hexpand (widget: label_widget, TRUE);
1158 gtk_widget_set_halign (widget: label_widget, align: GTK_ALIGN_CENTER);
1159 arrow = gtk_builtin_icon_new (css_name: "arrow");
1160 menu_button->arrow_widget = arrow;
1161 gtk_box_append (GTK_BOX (box), child: label_widget);
1162 gtk_box_append (GTK_BOX (box), child: arrow);
1163 gtk_button_set_child (GTK_BUTTON (menu_button->button), child: box);
1164 menu_button->label_widget = label_widget;
1165
1166 gtk_accessible_update_relation (self: GTK_ACCESSIBLE (ptr: menu_button->button),
1167 first_relation: GTK_ACCESSIBLE_RELATION_LABELLED_BY, menu_button->label_widget, NULL,
1168 GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, menu_button->label_widget, NULL,
1169 -1);
1170
1171 menu_button->image_widget = NULL;
1172 menu_button->child = NULL;
1173
1174 update_arrow (menu_button);
1175
1176 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_LABEL]);
1177
1178 g_object_thaw_notify (G_OBJECT (menu_button));
1179}
1180
1181/**
1182 * gtk_menu_button_get_label: (attributes org.gtk.Method.get_property=label)
1183 * @menu_button: a `GtkMenuButton`
1184 *
1185 * Gets the label shown in the button
1186 *
1187 * Returns: (nullable): the label shown in the button
1188 */
1189const char *
1190gtk_menu_button_get_label (GtkMenuButton *menu_button)
1191{
1192 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
1193
1194 if (menu_button->label_widget)
1195 return gtk_label_get_label (GTK_LABEL (menu_button->label_widget));
1196
1197 return NULL;
1198}
1199
1200/**
1201 * gtk_menu_button_set_has_frame: (attributes org.gtk.Method.set_property=has-frame)
1202 * @menu_button: a `GtkMenuButton`
1203 * @has_frame: whether the button should have a visible frame
1204 *
1205 * Sets the style of the button.
1206 */
1207void
1208gtk_menu_button_set_has_frame (GtkMenuButton *menu_button,
1209 gboolean has_frame)
1210{
1211 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1212
1213 if (gtk_button_get_has_frame (GTK_BUTTON (menu_button->button)) == has_frame)
1214 return;
1215
1216 gtk_button_set_has_frame (GTK_BUTTON (menu_button->button), has_frame);
1217 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_HAS_FRAME]);
1218}
1219
1220/**
1221 * gtk_menu_button_get_has_frame: (attributes org.gtk.Method.get_property=has-frame)
1222 * @menu_button: a `GtkMenuButton`
1223 *
1224 * Returns whether the button has a frame.
1225 *
1226 * Returns: %TRUE if the button has a frame
1227 */
1228gboolean
1229gtk_menu_button_get_has_frame (GtkMenuButton *menu_button)
1230{
1231 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), TRUE);
1232
1233 return gtk_button_get_has_frame (GTK_BUTTON (menu_button->button));
1234}
1235
1236/**
1237 * gtk_menu_button_popup:
1238 * @menu_button: a `GtkMenuButton`
1239 *
1240 * Pop up the menu.
1241 */
1242void
1243gtk_menu_button_popup (GtkMenuButton *menu_button)
1244{
1245 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1246
1247 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button->button), TRUE);
1248}
1249
1250/**
1251 * gtk_menu_button_popdown:
1252 * @menu_button: a `GtkMenuButton`
1253 *
1254 * Dismiss the menu.
1255 */
1256void
1257gtk_menu_button_popdown (GtkMenuButton *menu_button)
1258{
1259 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1260
1261 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button->button), FALSE);
1262}
1263
1264/**
1265 * gtk_menu_button_set_create_popup_func:
1266 * @menu_button: a `GtkMenuButton`
1267 * @func: (nullable): function to call when a popup is about to
1268 * be shown, but none has been provided via other means, or %NULL
1269 * to reset to default behavior.
1270 * @user_data: (closure): user data to pass to @func.
1271 * @destroy_notify: (nullable): destroy notify for @user_data
1272 *
1273 * Sets @func to be called when a popup is about to be shown.
1274 *
1275 * @func should use one of
1276 *
1277 * - [method@Gtk.MenuButton.set_popover]
1278 * - [method@Gtk.MenuButton.set_menu_model]
1279 *
1280 * to set a popup for @menu_button.
1281 * If @func is non-%NULL, @menu_button will always be sensitive.
1282 *
1283 * Using this function will not reset the menu widget attached to
1284 * @menu_button. Instead, this can be done manually in @func.
1285 */
1286void
1287gtk_menu_button_set_create_popup_func (GtkMenuButton *menu_button,
1288 GtkMenuButtonCreatePopupFunc func,
1289 gpointer user_data,
1290 GDestroyNotify destroy_notify)
1291{
1292 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1293
1294 if (menu_button->create_popup_destroy_notify)
1295 menu_button->create_popup_destroy_notify (menu_button->create_popup_user_data);
1296
1297 menu_button->create_popup_func = func;
1298 menu_button->create_popup_user_data = user_data;
1299 menu_button->create_popup_destroy_notify = destroy_notify;
1300
1301 update_sensitivity (self: menu_button);
1302}
1303
1304/**
1305 * gtk_menu_button_set_use_underline: (attributes org.gtk.Method.set_property=use-underline)
1306 * @menu_button: a `GtkMenuButton`
1307 * @use_underline: %TRUE if underlines in the text indicate mnemonics
1308 *
1309 * If true, an underline in the text indicates a mnemonic.
1310 */
1311void
1312gtk_menu_button_set_use_underline (GtkMenuButton *menu_button,
1313 gboolean use_underline)
1314{
1315 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1316
1317 if (gtk_button_get_use_underline (GTK_BUTTON (menu_button->button)) == use_underline)
1318 return;
1319
1320 gtk_button_set_use_underline (GTK_BUTTON (menu_button->button), use_underline);
1321 if (menu_button->label_widget)
1322 gtk_label_set_use_underline (GTK_LABEL (menu_button->label_widget), setting: use_underline);
1323
1324 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_USE_UNDERLINE]);
1325}
1326
1327/**
1328 * gtk_menu_button_get_use_underline: (attributes org.gtk.Method.get_property=use-underline)
1329 * @menu_button: a `GtkMenuButton`
1330 *
1331 * Returns whether an embedded underline in the text indicates a
1332 * mnemonic.
1333 *
1334 * Returns: %TRUE whether an embedded underline in the text indicates
1335 * the mnemonic accelerator keys.
1336 */
1337gboolean
1338gtk_menu_button_get_use_underline (GtkMenuButton *menu_button)
1339{
1340 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), FALSE);
1341
1342 return gtk_button_get_use_underline (GTK_BUTTON (menu_button->button));
1343}
1344
1345static GList *
1346get_menu_bars (GtkWindow *window)
1347{
1348 return g_object_get_data (G_OBJECT (window), key: "gtk-menu-bar-list");
1349}
1350
1351static void
1352set_menu_bars (GtkWindow *window,
1353 GList *menubars)
1354{
1355 g_object_set_data (G_OBJECT (window), I_("gtk-menu-bar-list"), data: menubars);
1356}
1357
1358static void
1359add_to_window (GtkWindow *window,
1360 GtkMenuButton *button)
1361{
1362 GList *menubars = get_menu_bars (window);
1363
1364 set_menu_bars (window, menubars: g_list_prepend (list: menubars, data: button));
1365}
1366
1367static void
1368remove_from_window (GtkWindow *window,
1369 GtkMenuButton *button)
1370{
1371 GList *menubars = get_menu_bars (window);
1372
1373 menubars = g_list_remove (list: menubars, data: button);
1374 set_menu_bars (window, menubars);
1375}
1376
1377static void
1378gtk_menu_button_root (GtkWidget *widget)
1379{
1380 GtkMenuButton *button = GTK_MENU_BUTTON (widget);
1381
1382 GTK_WIDGET_CLASS (gtk_menu_button_parent_class)->root (widget);
1383
1384 if (button->primary)
1385 {
1386 GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
1387 add_to_window (GTK_WINDOW (toplevel), button);
1388 }
1389}
1390
1391static void
1392gtk_menu_button_unroot (GtkWidget *widget)
1393{
1394 GtkWidget *toplevel;
1395
1396 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
1397 remove_from_window (GTK_WINDOW (toplevel), GTK_MENU_BUTTON (widget));
1398
1399 GTK_WIDGET_CLASS (gtk_menu_button_parent_class)->unroot (widget);
1400}
1401
1402/**
1403 * gtk_menu_button_set_primary: (attributes org.gtk.Method.set_property=primary)
1404 * @menu_button: a `GtkMenuButton`
1405 * @primary: whether the menubutton should act as a primary menu
1406 *
1407 * Sets whether menu button acts as a primary menu.
1408 *
1409 * Primary menus can be opened with the <kbd>F10</kbd> key.
1410 *
1411 * Since: 4.4
1412 */
1413void
1414gtk_menu_button_set_primary (GtkMenuButton *menu_button,
1415 gboolean primary)
1416{
1417 GtkRoot *toplevel;
1418
1419 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1420
1421 if (menu_button->primary == primary)
1422 return;
1423
1424 menu_button->primary = primary;
1425 toplevel = gtk_widget_get_root (GTK_WIDGET (menu_button));
1426
1427 if (toplevel)
1428 {
1429 if (menu_button->primary)
1430 add_to_window (GTK_WINDOW (toplevel), button: menu_button);
1431 else
1432 remove_from_window (GTK_WINDOW (toplevel), button: menu_button);
1433 }
1434
1435 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_PRIMARY]);
1436}
1437
1438/**
1439 * gtk_menu_button_get_primary: (attributes org.gtk.Method.get_property=primary)
1440 * @menu_button: a `GtkMenuButton`
1441 *
1442 * Returns whether the menu button acts as a primary menu.
1443 *
1444 * Returns: %TRUE if the button is a primary menu
1445 *
1446 * Since: 4.4
1447 */
1448gboolean
1449gtk_menu_button_get_primary (GtkMenuButton *menu_button)
1450{
1451 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), FALSE);
1452
1453 return menu_button->primary;
1454}
1455
1456/**
1457 * gtk_menu_button_set_child: (attributes org.gtk.Method.set_property=child)
1458 * @menu_button: a `GtkMenuButton`
1459 * @child: (nullable): the child widget
1460 *
1461 * Sets the child widget of @menu_button.
1462 *
1463 * Setting a child resets [property@Gtk.MenuButton:label] and
1464 * [property@Gtk.MenuButton:icon-name].
1465 *
1466 * If [property@Gtk.MenuButton:always-show-arrow] is set to `TRUE` and
1467 * [property@Gtk.MenuButton:direction] is not `GTK_ARROW_NONE`, a dropdown arrow
1468 * will be shown next to the child.
1469 *
1470 * Since: 4.6
1471 */
1472void
1473gtk_menu_button_set_child (GtkMenuButton *menu_button,
1474 GtkWidget *child)
1475{
1476 GtkWidget *box, *arrow;
1477
1478 g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button));
1479 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
1480
1481 g_object_freeze_notify (G_OBJECT (menu_button));
1482
1483 if (gtk_menu_button_get_label (menu_button))
1484 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_LABEL]);
1485 if (gtk_menu_button_get_icon_name (menu_button))
1486 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_ICON_NAME]);
1487
1488 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
1489 gtk_widget_set_halign (widget: box, align: GTK_ALIGN_CENTER);
1490
1491 arrow = gtk_builtin_icon_new (css_name: "arrow");
1492 menu_button->arrow_widget = arrow;
1493
1494 gtk_box_append (GTK_BOX (box), child);
1495 gtk_box_append (GTK_BOX (box), child: arrow);
1496 gtk_button_set_child (GTK_BUTTON (menu_button->button), child: box);
1497
1498 menu_button->child = child;
1499
1500 menu_button->image_widget = NULL;
1501 menu_button->label_widget = NULL;
1502
1503 update_arrow (menu_button);
1504
1505 g_object_notify_by_pspec (G_OBJECT (menu_button), pspec: menu_button_props[PROP_CHILD]);
1506
1507 g_object_thaw_notify (G_OBJECT (menu_button));
1508}
1509
1510/**
1511 * gtk_menu_button_get_child: (attributes org.gtk.Method.get_property=child)
1512 * @menu_button: a `GtkMenuButton`
1513 *
1514 * Gets the child widget of @menu_button.
1515 *
1516 * Returns: (nullable) (transfer none): the child widget of @menu_button
1517 *
1518 * Since: 4.6
1519 */
1520GtkWidget *
1521gtk_menu_button_get_child (GtkMenuButton *menu_button)
1522{
1523 g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL);
1524
1525 return menu_button->child;
1526}
1527

source code of gtk/gtk/gtkmenubutton.c