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 | |
109 | typedef struct _GtkMenuButtonClass ; |
110 | typedef struct ; |
111 | |
112 | struct |
113 | { |
114 | GtkWidget ; |
115 | |
116 | GtkWidget *; |
117 | GtkWidget *; /* Only one at a time can be set */ |
118 | GMenuModel *; |
119 | |
120 | GtkMenuButtonCreatePopupFunc ; |
121 | gpointer ; |
122 | GDestroyNotify ; |
123 | |
124 | GtkWidget *; |
125 | GtkWidget *; |
126 | GtkWidget *; |
127 | GtkWidget *; |
128 | GtkArrowType ; |
129 | gboolean ; |
130 | |
131 | gboolean ; |
132 | }; |
133 | |
134 | struct |
135 | { |
136 | GtkWidgetClass ; |
137 | |
138 | void (* ) (GtkMenuButton *self); |
139 | }; |
140 | |
141 | enum |
142 | { |
143 | PROP_0, |
144 | , |
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 | |
157 | enum { |
158 | ACTIVATE, |
159 | LAST_SIGNAL |
160 | }; |
161 | |
162 | static GParamSpec *[LAST_PROP]; |
163 | static guint signals[LAST_SIGNAL] = { 0 }; |
164 | |
165 | static void gtk_menu_button_buildable_iface_init (GtkBuildableIface *iface); |
166 | |
167 | G_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 | |
170 | static void gtk_menu_button_dispose (GObject *object); |
171 | |
172 | static void |
173 | (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 | |
217 | static void |
218 | (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 | |
262 | static void |
263 | (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 | |
278 | static void |
279 | (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 | |
291 | static void |
292 | (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 | |
320 | static void |
321 | (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 | |
339 | static void |
340 | (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 | |
354 | static gboolean |
355 | (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 | |
366 | static gboolean |
367 | (GtkWidget *widget) |
368 | { |
369 | GtkMenuButton *self = GTK_MENU_BUTTON (widget); |
370 | |
371 | return gtk_widget_grab_focus (widget: self->button); |
372 | } |
373 | |
374 | static void |
375 | (GtkMenuButton *self) |
376 | { |
377 | gtk_widget_activate (widget: self->button); |
378 | } |
379 | |
380 | static void gtk_menu_button_root (GtkWidget *widget); |
381 | static void gtk_menu_button_unroot (GtkWidget *widget); |
382 | |
383 | static void |
384 | (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 | |
565 | static void |
566 | set_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 | |
602 | static void |
603 | update_style_classes (GtkMenuButton *) |
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 | |
626 | static void |
627 | update_arrow (GtkMenuButton *) |
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 | |
646 | static void |
647 | add_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 | |
658 | static void |
659 | (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 | |
679 | static GtkBuildableIface *parent_buildable_iface; |
680 | |
681 | static void |
682 | (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 | |
693 | static void |
694 | (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 | */ |
712 | GtkWidget * |
713 | (void) |
714 | { |
715 | return g_object_new (GTK_TYPE_MENU_BUTTON, NULL); |
716 | } |
717 | |
718 | static void |
719 | update_sensitivity (GtkMenuButton *self) |
720 | { |
721 | gboolean ; |
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 | |
739 | static gboolean |
740 | (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 | */ |
764 | void |
765 | (GtkMenuButton *, |
766 | GMenuModel *) |
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 | */ |
802 | GMenuModel * |
803 | (GtkMenuButton *) |
804 | { |
805 | g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL); |
806 | |
807 | return menu_button->model; |
808 | } |
809 | |
810 | static void |
811 | update_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 | |
836 | static void |
837 | popover_destroy_cb (GtkMenuButton *) |
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 | */ |
858 | void |
859 | (GtkMenuButton *, |
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 | */ |
882 | GtkArrowType |
883 | (GtkMenuButton *) |
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 | |
890 | static void |
891 | (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 | */ |
928 | void |
929 | (GtkMenuButton *, |
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 | */ |
984 | GtkPopover * |
985 | (GtkMenuButton *) |
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 | */ |
1006 | void |
1007 | (GtkMenuButton *, |
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 | */ |
1065 | const char * |
1066 | (GtkMenuButton *) |
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 | */ |
1086 | void |
1087 | (GtkMenuButton *, |
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 | */ |
1114 | gboolean |
1115 | (GtkMenuButton *) |
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 | */ |
1135 | void |
1136 | (GtkMenuButton *, |
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 | */ |
1189 | const char * |
1190 | (GtkMenuButton *) |
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 | */ |
1207 | void |
1208 | (GtkMenuButton *, |
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 | */ |
1228 | gboolean |
1229 | (GtkMenuButton *) |
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 | */ |
1242 | void |
1243 | (GtkMenuButton *) |
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 | */ |
1256 | void |
1257 | (GtkMenuButton *) |
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 | */ |
1286 | void |
1287 | (GtkMenuButton *, |
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 | */ |
1311 | void |
1312 | (GtkMenuButton *, |
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 | */ |
1337 | gboolean |
1338 | (GtkMenuButton *) |
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 | |
1345 | static GList * |
1346 | (GtkWindow *window) |
1347 | { |
1348 | return g_object_get_data (G_OBJECT (window), key: "gtk-menu-bar-list" ); |
1349 | } |
1350 | |
1351 | static void |
1352 | (GtkWindow *window, |
1353 | GList *) |
1354 | { |
1355 | g_object_set_data (G_OBJECT (window), I_("gtk-menu-bar-list" ), data: menubars); |
1356 | } |
1357 | |
1358 | static void |
1359 | add_to_window (GtkWindow *window, |
1360 | GtkMenuButton *button) |
1361 | { |
1362 | GList * = get_menu_bars (window); |
1363 | |
1364 | set_menu_bars (window, menubars: g_list_prepend (list: menubars, data: button)); |
1365 | } |
1366 | |
1367 | static void |
1368 | remove_from_window (GtkWindow *window, |
1369 | GtkMenuButton *button) |
1370 | { |
1371 | GList * = get_menu_bars (window); |
1372 | |
1373 | menubars = g_list_remove (list: menubars, data: button); |
1374 | set_menu_bars (window, menubars); |
1375 | } |
1376 | |
1377 | static void |
1378 | (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 | |
1391 | static void |
1392 | (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 | */ |
1413 | void |
1414 | (GtkMenuButton *, |
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 | */ |
1448 | gboolean |
1449 | (GtkMenuButton *) |
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 | */ |
1472 | void |
1473 | (GtkMenuButton *, |
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 | */ |
1520 | GtkWidget * |
1521 | (GtkMenuButton *) |
1522 | { |
1523 | g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL); |
1524 | |
1525 | return menu_button->child; |
1526 | } |
1527 | |