1/* GTK - The GIMP Toolkit
2 *
3 * Copyright (C) 2003 Sun Microsystems, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authors:
19 * Mark McLoughlin <mark@skynet.ie>
20 */
21
22/**
23 * GtkExpander:
24 *
25 * `GtkExpander` allows the user to reveal its child by clicking
26 * on an expander triangle.
27 *
28 * ![An example GtkExpander](expander.png)
29 *
30 * This is similar to the triangles used in a `GtkTreeView`.
31 *
32 * Normally you use an expander as you would use a frame; you create
33 * the child widget and use [method@Gtk.Expander.set_child] to add it
34 * to the expander. When the expander is toggled, it will take care of
35 * showing and hiding the child automatically.
36 *
37 * # Special Usage
38 *
39 * There are situations in which you may prefer to show and hide the
40 * expanded widget yourself, such as when you want to actually create
41 * the widget at expansion time. In this case, create a `GtkExpander`
42 * but do not add a child to it. The expander widget has an
43 * [property@Gtk.Expander:expanded[ property which can be used to
44 * monitor its expansion state. You should watch this property with
45 * a signal connection as follows:
46 *
47 * ```c
48 * static void
49 * expander_callback (GObject *object,
50 * GParamSpec *param_spec,
51 * gpointer user_data)
52 * {
53 * GtkExpander *expander;
54 *
55 * expander = GTK_EXPANDER (object);
56 *
57 * if (gtk_expander_get_expanded (expander))
58 * {
59 * // Show or create widgets
60 * }
61 * else
62 * {
63 * // Hide or destroy widgets
64 * }
65 * }
66 *
67 * static void
68 * create_expander (void)
69 * {
70 * GtkWidget *expander = gtk_expander_new_with_mnemonic ("_More Options");
71 * g_signal_connect (expander, "notify::expanded",
72 * G_CALLBACK (expander_callback), NULL);
73 *
74 * // ...
75 * }
76 * ```
77 *
78 * # GtkExpander as GtkBuildable
79 *
80 * The `GtkExpander` implementation of the `GtkBuildable` interface supports
81 * placing a child in the label position by specifying “label” as the
82 * “type” attribute of a <child> element. A normal content child can be
83 * specified without specifying a <child> type attribute.
84 *
85 * An example of a UI definition fragment with GtkExpander:
86 *
87 * ```xml
88 * <object class="GtkExpander">
89 * <child type="label">
90 * <object class="GtkLabel" id="expander-label"/>
91 * </child>
92 * <child>
93 * <object class="GtkEntry" id="expander-content"/>
94 * </child>
95 * </object>
96 * ```
97 *
98 * # CSS nodes
99 *
100 * ```
101 * expander
102 * ╰── box
103 * ├── title
104 * │ ├── arrow
105 * │ ╰── <label widget>
106 * ╰── <child>
107 * ```
108 *
109 * `GtkExpander` has three CSS nodes, the main node with the name expander,
110 * a subnode with name title and node below it with name arrow. The arrow of an
111 * expander that is showing its child gets the :checked pseudoclass added to it.
112 *
113 * # Accessibility
114 *
115 * `GtkExpander` uses the %GTK_ACCESSIBLE_ROLE_BUTTON role.
116 */
117
118#include "config.h"
119
120#include "gtkexpander.h"
121
122#include "gtkbox.h"
123#include "gtkbuildable.h"
124#include "gtkdropcontrollermotion.h"
125#include "gtkbuiltiniconprivate.h"
126#include "gtkgestureclick.h"
127#include "gtkgesturesingle.h"
128#include "gtkintl.h"
129#include "gtklabel.h"
130#include "gtkmarshalers.h"
131#include "gtkmain.h"
132#include "gtkprivate.h"
133#include "gtkwidgetprivate.h"
134
135#include <string.h>
136
137#define TIMEOUT_EXPAND 500
138
139enum
140{
141 PROP_0,
142 PROP_EXPANDED,
143 PROP_LABEL,
144 PROP_USE_UNDERLINE,
145 PROP_USE_MARKUP,
146 PROP_LABEL_WIDGET,
147 PROP_RESIZE_TOPLEVEL,
148 PROP_CHILD
149};
150
151typedef struct _GtkExpanderClass GtkExpanderClass;
152
153struct _GtkExpander
154{
155 GtkWidget parent_instance;
156
157 GtkWidget *label_widget;
158
159 GtkWidget *box;
160 GtkWidget *title_widget;
161 GtkWidget *arrow_widget;
162 GtkWidget *child;
163
164 guint expand_timer;
165
166 guint expanded : 1;
167 guint use_underline : 1;
168 guint use_markup : 1;
169 guint resize_toplevel : 1;
170};
171
172struct _GtkExpanderClass
173{
174 GtkWidgetClass parent_class;
175
176 void (* activate) (GtkExpander *expander);
177};
178
179static void gtk_expander_dispose (GObject *object);
180static void gtk_expander_set_property (GObject *object,
181 guint prop_id,
182 const GValue *value,
183 GParamSpec *pspec);
184static void gtk_expander_get_property (GObject *object,
185 guint prop_id,
186 GValue *value,
187 GParamSpec *pspec);
188
189static void gtk_expander_size_allocate (GtkWidget *widget,
190 int width,
191 int height,
192 int baseline);
193static gboolean gtk_expander_focus (GtkWidget *widget,
194 GtkDirectionType direction);
195
196static void gtk_expander_activate (GtkExpander *expander);
197
198
199/* GtkBuildable */
200static void gtk_expander_buildable_init (GtkBuildableIface *iface);
201static void gtk_expander_buildable_add_child (GtkBuildable *buildable,
202 GtkBuilder *builder,
203 GObject *child,
204 const char *type);
205
206
207/* GtkWidget */
208static void gtk_expander_measure (GtkWidget *widget,
209 GtkOrientation orientation,
210 int for_size,
211 int *minimum,
212 int *natural,
213 int *minimum_baseline,
214 int *natural_baseline);
215
216/* Gestures */
217static void gesture_click_released_cb (GtkGestureClick *gesture,
218 int n_press,
219 double x,
220 double y,
221 GtkExpander *expander);
222
223G_DEFINE_TYPE_WITH_CODE (GtkExpander, gtk_expander, GTK_TYPE_WIDGET,
224 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
225 gtk_expander_buildable_init))
226
227static gboolean
228expand_timeout (gpointer data)
229{
230 GtkExpander *expander = GTK_EXPANDER (data);
231
232 expander->expand_timer = 0;
233 gtk_expander_set_expanded (expander, TRUE);
234
235 return FALSE;
236}
237
238static void
239gtk_expander_drag_enter (GtkDropControllerMotion *motion,
240 double x,
241 double y,
242 GtkExpander *expander)
243{
244 if (!expander->expanded && !expander->expand_timer)
245 {
246 expander->expand_timer = g_timeout_add (TIMEOUT_EXPAND, function: (GSourceFunc) expand_timeout, data: expander);
247 gdk_source_set_static_name_by_id (tag: expander->expand_timer, name: "[gtk] expand_timeout");
248 }
249}
250
251static void
252gtk_expander_drag_leave (GtkDropControllerMotion *motion,
253 GtkExpander *expander)
254{
255 if (expander->expand_timer)
256 {
257 g_source_remove (tag: expander->expand_timer);
258 expander->expand_timer = 0;
259 }
260}
261
262static GtkSizeRequestMode
263gtk_expander_get_request_mode (GtkWidget *widget)
264{
265 GtkExpander *expander = GTK_EXPANDER (widget);
266
267 if (expander->child)
268 return gtk_widget_get_request_mode (widget: expander->child);
269 else
270 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
271}
272
273static void
274gtk_expander_compute_expand (GtkWidget *widget,
275 gboolean *hexpand,
276 gboolean *vexpand)
277{
278 GtkExpander *expander = GTK_EXPANDER (widget);
279
280 if (expander->child)
281 {
282 *hexpand = gtk_widget_compute_expand (widget: expander->child, orientation: GTK_ORIENTATION_HORIZONTAL);
283 *vexpand = gtk_widget_compute_expand (widget: expander->child, orientation: GTK_ORIENTATION_VERTICAL);
284 }
285 else
286 {
287 *hexpand = FALSE;
288 *vexpand = FALSE;
289 }
290}
291
292static void
293gtk_expander_class_init (GtkExpanderClass *klass)
294{
295 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
296 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
297 guint activate_signal;
298
299 gobject_class->dispose = gtk_expander_dispose;
300 gobject_class->set_property = gtk_expander_set_property;
301 gobject_class->get_property = gtk_expander_get_property;
302
303 widget_class->size_allocate = gtk_expander_size_allocate;
304 widget_class->focus = gtk_expander_focus;
305 widget_class->grab_focus = gtk_widget_grab_focus_self;
306 widget_class->measure = gtk_expander_measure;
307 widget_class->compute_expand = gtk_expander_compute_expand;
308 widget_class->get_request_mode = gtk_expander_get_request_mode;
309
310 klass->activate = gtk_expander_activate;
311
312 /**
313 * GtkExpander:expanded: (attributes org.gtk.Property.get=gtk_expander_get_expanded org.gtk.Property.set=gtk_expander_set_expanded)
314 *
315 * Whether the expander has been opened to reveal the child.
316 */
317 g_object_class_install_property (oclass: gobject_class,
318 property_id: PROP_EXPANDED,
319 pspec: g_param_spec_boolean (name: "expanded",
320 P_("Expanded"),
321 P_("Whether the expander has been opened to reveal the child widget"),
322 FALSE,
323 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY));
324
325 /**
326 * GtkExpander:label: (attributes org.gtk.Property.get=gtk_expander_get_label org.gtk.Property.set=gtk_expander_set_label)
327 *
328 * The text of the expanders label.
329 */
330 g_object_class_install_property (oclass: gobject_class,
331 property_id: PROP_LABEL,
332 pspec: g_param_spec_string (name: "label",
333 P_("Label"),
334 P_("Text of the expander’s label"),
335 NULL,
336 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT));
337
338 /**
339 * GtkExpander:use-underline: (attributes org.gtk.Property.get=gtk_expander_get_use_underline org.gtk.Property.set=gtk_expander_set_use_underline)
340 *
341 * Whether an underline in the text indicates a mnemonic.
342 */
343 g_object_class_install_property (oclass: gobject_class,
344 property_id: PROP_USE_UNDERLINE,
345 pspec: g_param_spec_boolean (name: "use-underline",
346 P_("Use underline"),
347 P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
348 FALSE,
349 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY));
350
351 /**
352 * GtkExpander:use-markup: (attributes org.gtk.Property.get=gtk_expander_get_use_markup org.gtk.Property.set=gtk_expander_set_use_markup)
353 *
354 * Whether the text in the label is Pango markup.
355 */
356 g_object_class_install_property (oclass: gobject_class,
357 property_id: PROP_USE_MARKUP,
358 pspec: g_param_spec_boolean (name: "use-markup",
359 P_("Use markup"),
360 P_("The text of the label includes XML markup. See pango_parse_markup()"),
361 FALSE,
362 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY));
363
364 /**
365 * GtkExpander:label-widget: (attributes org.gtk.Property.get=gtk_expander_get_label_widget org.gtk.Property.set=gtk_expander_set_label_widget)
366 *
367 * A widget to display instead of the usual expander label.
368 */
369 g_object_class_install_property (oclass: gobject_class,
370 property_id: PROP_LABEL_WIDGET,
371 pspec: g_param_spec_object (name: "label-widget",
372 P_("Label widget"),
373 P_("A widget to display in place of the usual expander label"),
374 GTK_TYPE_WIDGET,
375 GTK_PARAM_READWRITE));
376
377 /**
378 * GtkExpander:resize-toplevel: (attributes org.gtk.Property.get=gtk_expander_get_resize_toplevel org.gtk.Property.set=gtk_expander_set_resize_toplevel)
379 *
380 * When this property is %TRUE, the expander will resize the toplevel
381 * widget containing the expander upon expanding and collapsing.
382 */
383 g_object_class_install_property (oclass: gobject_class,
384 property_id: PROP_RESIZE_TOPLEVEL,
385 pspec: g_param_spec_boolean (name: "resize-toplevel",
386 P_("Resize toplevel"),
387 P_("Whether the expander will resize the toplevel window upon expanding and collapsing"),
388 FALSE,
389 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
390
391 /**
392 * GtkExpander:child: (attributes org.gtk.Property.get=gtk_expander_get_child org.gtk.Property.set=gtk_expander_set_child)
393 *
394 * The child widget.
395 */
396 g_object_class_install_property (oclass: gobject_class,
397 property_id: PROP_CHILD,
398 pspec: g_param_spec_object (name: "child",
399 P_("Child"),
400 P_("The child widget"),
401 GTK_TYPE_WIDGET,
402 GTK_PARAM_READWRITE));
403
404 /**
405 * GtkExpander::activate:
406 * @expander: the `GtkExpander` that emitted the signal
407 *
408 * Activates the `GtkExpander`.
409 */
410 activate_signal =
411 g_signal_new (I_("activate"),
412 G_TYPE_FROM_CLASS (gobject_class),
413 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
414 G_STRUCT_OFFSET (GtkExpanderClass, activate),
415 NULL, NULL,
416 NULL,
417 G_TYPE_NONE, n_params: 0);
418
419 gtk_widget_class_set_activate_signal (widget_class, signal_id: activate_signal);
420 gtk_widget_class_set_css_name (widget_class, I_("expander-widget"));
421 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_BUTTON);
422}
423
424static void
425gtk_expander_init (GtkExpander *expander)
426{
427 GtkGesture *gesture;
428 GtkEventController *controller;
429
430 expander->label_widget = NULL;
431 expander->child = NULL;
432
433 expander->expanded = FALSE;
434 expander->use_underline = FALSE;
435 expander->use_markup = FALSE;
436 expander->expand_timer = 0;
437 expander->resize_toplevel = 0;
438
439 gtk_widget_set_focusable (GTK_WIDGET (expander), TRUE);
440
441 expander->box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
442 gtk_widget_set_parent (widget: expander->box, GTK_WIDGET (expander));
443
444 expander->title_widget = g_object_new (GTK_TYPE_BOX,
445 first_property_name: "css-name", "title",
446 NULL);
447 gtk_box_append (GTK_BOX (expander->box), child: expander->title_widget);
448
449 expander->arrow_widget = gtk_builtin_icon_new (css_name: "expander");
450 gtk_widget_add_css_class (widget: expander->arrow_widget, css_class: "horizontal");
451 gtk_box_append (GTK_BOX (expander->title_widget), child: expander->arrow_widget);
452
453 controller = gtk_drop_controller_motion_new ();
454 g_signal_connect (controller, "enter", G_CALLBACK (gtk_expander_drag_enter), expander);
455 g_signal_connect (controller, "leave", G_CALLBACK (gtk_expander_drag_leave), expander);
456 gtk_widget_add_controller (GTK_WIDGET (expander), controller);
457
458 gesture = gtk_gesture_click_new ();
459 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
460 GDK_BUTTON_PRIMARY);
461 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
462 FALSE);
463 g_signal_connect (gesture, "released",
464 G_CALLBACK (gesture_click_released_cb), expander);
465 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
466 phase: GTK_PHASE_BUBBLE);
467 gtk_widget_add_controller (GTK_WIDGET (expander->title_widget), GTK_EVENT_CONTROLLER (gesture));
468
469 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: expander),
470 first_state: GTK_ACCESSIBLE_STATE_EXPANDED, FALSE,
471 -1);
472}
473
474static GtkBuildableIface *parent_buildable_iface;
475
476static void
477gtk_expander_buildable_add_child (GtkBuildable *buildable,
478 GtkBuilder *builder,
479 GObject *child,
480 const char *type)
481{
482 if (g_strcmp0 (str1: type, str2: "label") == 0)
483 gtk_expander_set_label_widget (GTK_EXPANDER (buildable), GTK_WIDGET (child));
484 else if (GTK_IS_WIDGET (child))
485 gtk_expander_set_child (GTK_EXPANDER (buildable), GTK_WIDGET (child));
486 else
487 parent_buildable_iface->add_child (buildable, builder, child, type);
488}
489
490static void
491gtk_expander_buildable_init (GtkBuildableIface *iface)
492{
493 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
494
495 iface->add_child = gtk_expander_buildable_add_child;
496}
497
498static void
499gtk_expander_set_property (GObject *object,
500 guint prop_id,
501 const GValue *value,
502 GParamSpec *pspec)
503{
504 GtkExpander *expander = GTK_EXPANDER (object);
505
506 switch (prop_id)
507 {
508 case PROP_EXPANDED:
509 gtk_expander_set_expanded (expander, expanded: g_value_get_boolean (value));
510 break;
511 case PROP_LABEL:
512 gtk_expander_set_label (expander, label: g_value_get_string (value));
513 break;
514 case PROP_USE_UNDERLINE:
515 gtk_expander_set_use_underline (expander, use_underline: g_value_get_boolean (value));
516 break;
517 case PROP_USE_MARKUP:
518 gtk_expander_set_use_markup (expander, use_markup: g_value_get_boolean (value));
519 break;
520 case PROP_LABEL_WIDGET:
521 gtk_expander_set_label_widget (expander, label_widget: g_value_get_object (value));
522 break;
523 case PROP_RESIZE_TOPLEVEL:
524 gtk_expander_set_resize_toplevel (expander, resize_toplevel: g_value_get_boolean (value));
525 break;
526 case PROP_CHILD:
527 gtk_expander_set_child (expander, child: g_value_get_object (value));
528 break;
529 default:
530 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
531 break;
532 }
533}
534
535static void
536gtk_expander_get_property (GObject *object,
537 guint prop_id,
538 GValue *value,
539 GParamSpec *pspec)
540{
541 GtkExpander *expander = GTK_EXPANDER (object);
542
543 switch (prop_id)
544 {
545 case PROP_EXPANDED:
546 g_value_set_boolean (value, v_boolean: expander->expanded);
547 break;
548 case PROP_LABEL:
549 g_value_set_string (value, v_string: gtk_expander_get_label (expander));
550 break;
551 case PROP_USE_UNDERLINE:
552 g_value_set_boolean (value, v_boolean: expander->use_underline);
553 break;
554 case PROP_USE_MARKUP:
555 g_value_set_boolean (value, v_boolean: expander->use_markup);
556 break;
557 case PROP_LABEL_WIDGET:
558 g_value_set_object (value,
559 v_object: expander->label_widget ?
560 G_OBJECT (expander->label_widget) : NULL);
561 break;
562 case PROP_RESIZE_TOPLEVEL:
563 g_value_set_boolean (value, v_boolean: gtk_expander_get_resize_toplevel (expander));
564 break;
565 case PROP_CHILD:
566 g_value_set_object (value, v_object: gtk_expander_get_child (expander));
567 break;
568 default:
569 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
570 break;
571 }
572}
573
574static void
575gtk_expander_dispose (GObject *object)
576{
577 GtkExpander *expander = GTK_EXPANDER (object);
578
579 if (expander->expand_timer)
580 {
581 g_source_remove (tag: expander->expand_timer);
582 expander->expand_timer = 0;
583 }
584
585 /* If the expander is not expanded, we own the child */
586 if (!expander->expanded)
587 g_clear_object (&expander->child);
588
589 if (expander->box)
590 {
591 gtk_widget_unparent (widget: expander->box);
592 expander->box = NULL;
593 expander->child = NULL;
594 expander->label_widget = NULL;
595 expander->arrow_widget = NULL;
596 }
597
598 G_OBJECT_CLASS (gtk_expander_parent_class)->dispose (object);
599}
600
601static void
602gtk_expander_size_allocate (GtkWidget *widget,
603 int width,
604 int height,
605 int baseline)
606{
607 GtkExpander *expander = GTK_EXPANDER (widget);
608
609 gtk_widget_size_allocate (widget: expander->box,
610 allocation: &(GtkAllocation) {
611 0, 0,
612 width, height
613 }, baseline);
614}
615
616static void
617gesture_click_released_cb (GtkGestureClick *gesture,
618 int n_press,
619 double x,
620 double y,
621 GtkExpander *expander)
622{
623 gtk_widget_activate (GTK_WIDGET (expander));
624}
625
626typedef enum
627{
628 FOCUS_NONE,
629 FOCUS_WIDGET,
630 FOCUS_LABEL,
631 FOCUS_CHILD
632} FocusSite;
633
634static gboolean
635focus_current_site (GtkExpander *expander,
636 GtkDirectionType direction)
637{
638 GtkWidget *current_focus;
639
640 current_focus = gtk_widget_get_focus_child (GTK_WIDGET (expander));
641
642 if (!current_focus)
643 return FALSE;
644
645 return gtk_widget_child_focus (widget: current_focus, direction);
646}
647
648static gboolean
649focus_in_site (GtkExpander *expander,
650 FocusSite site,
651 GtkDirectionType direction)
652{
653 switch (site)
654 {
655 case FOCUS_WIDGET:
656 gtk_widget_grab_focus (GTK_WIDGET (expander));
657 return TRUE;
658 case FOCUS_LABEL:
659 if (expander->label_widget)
660 return gtk_widget_child_focus (widget: expander->label_widget, direction);
661 else
662 return FALSE;
663 case FOCUS_CHILD:
664 {
665 GtkWidget *child = expander->child;
666
667 if (child && gtk_widget_get_child_visible (widget: child))
668 return gtk_widget_child_focus (widget: child, direction);
669 else
670 return FALSE;
671 }
672 case FOCUS_NONE:
673 default:
674 break;
675 }
676
677 g_assert_not_reached ();
678 return FALSE;
679}
680
681static FocusSite
682get_next_site (GtkExpander *expander,
683 FocusSite site,
684 GtkDirectionType direction)
685{
686 gboolean ltr;
687
688 ltr = gtk_widget_get_direction (GTK_WIDGET (expander)) != GTK_TEXT_DIR_RTL;
689
690 switch (site)
691 {
692 case FOCUS_NONE:
693 switch (direction)
694 {
695 case GTK_DIR_TAB_BACKWARD:
696 case GTK_DIR_LEFT:
697 case GTK_DIR_UP:
698 return FOCUS_CHILD;
699 case GTK_DIR_TAB_FORWARD:
700 case GTK_DIR_DOWN:
701 case GTK_DIR_RIGHT:
702 default:
703 return FOCUS_WIDGET;
704 }
705 break;
706 case FOCUS_WIDGET:
707 switch (direction)
708 {
709 case GTK_DIR_TAB_BACKWARD:
710 case GTK_DIR_UP:
711 return FOCUS_NONE;
712 case GTK_DIR_LEFT:
713 return ltr ? FOCUS_NONE : FOCUS_LABEL;
714 case GTK_DIR_TAB_FORWARD:
715 case GTK_DIR_DOWN:
716 default:
717 return FOCUS_LABEL;
718 case GTK_DIR_RIGHT:
719 return ltr ? FOCUS_LABEL : FOCUS_NONE;
720 }
721 break;
722 case FOCUS_LABEL:
723 switch (direction)
724 {
725 case GTK_DIR_TAB_BACKWARD:
726 case GTK_DIR_UP:
727 return FOCUS_WIDGET;
728 case GTK_DIR_LEFT:
729 return ltr ? FOCUS_WIDGET : FOCUS_CHILD;
730 case GTK_DIR_TAB_FORWARD:
731 case GTK_DIR_DOWN:
732 default:
733 return FOCUS_CHILD;
734 case GTK_DIR_RIGHT:
735 return ltr ? FOCUS_CHILD : FOCUS_WIDGET;
736 }
737 break;
738 case FOCUS_CHILD:
739 switch (direction)
740 {
741 case GTK_DIR_TAB_BACKWARD:
742 case GTK_DIR_LEFT:
743 case GTK_DIR_UP:
744 return FOCUS_LABEL;
745 case GTK_DIR_TAB_FORWARD:
746 case GTK_DIR_DOWN:
747 case GTK_DIR_RIGHT:
748 default:
749 return FOCUS_NONE;
750 }
751 break;
752 default:
753 g_assert_not_reached ();
754 break;
755 }
756
757 return FOCUS_NONE;
758}
759
760static void
761gtk_expander_resize_toplevel (GtkExpander *expander)
762{
763 GtkWidget *child = expander->child;
764
765 if (child && expander->resize_toplevel &&
766 gtk_widget_get_realized (GTK_WIDGET (expander)))
767 {
768 GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (expander)));
769
770 if (GTK_IS_WINDOW (toplevel) &&
771 gtk_widget_get_realized (widget: toplevel))
772 gtk_widget_queue_resize (GTK_WIDGET (expander));
773 }
774}
775
776static gboolean
777gtk_expander_focus (GtkWidget *widget,
778 GtkDirectionType direction)
779{
780 GtkExpander *expander = GTK_EXPANDER (widget);
781
782 if (!focus_current_site (expander, direction))
783 {
784 GtkWidget *old_focus_child;
785 gboolean widget_is_focus;
786 FocusSite site = FOCUS_NONE;
787
788 widget_is_focus = gtk_widget_is_focus (widget);
789 old_focus_child = gtk_widget_get_focus_child (GTK_WIDGET (widget));
790
791 if (old_focus_child && old_focus_child == expander->label_widget)
792 site = FOCUS_LABEL;
793 else if (old_focus_child)
794 site = FOCUS_CHILD;
795 else if (widget_is_focus)
796 site = FOCUS_WIDGET;
797
798 while ((site = get_next_site (expander, site, direction)) != FOCUS_NONE)
799 {
800 if (focus_in_site (expander, site, direction))
801 return TRUE;
802 }
803
804 return FALSE;
805 }
806
807 return TRUE;
808}
809
810static void
811gtk_expander_activate (GtkExpander *expander)
812{
813 gtk_expander_set_expanded (expander, expanded: !expander->expanded);
814}
815
816static void
817gtk_expander_measure (GtkWidget *widget,
818 GtkOrientation orientation,
819 int for_size,
820 int *minimum,
821 int *natural,
822 int *minimum_baseline,
823 int *natural_baseline)
824{
825 GtkExpander *expander = GTK_EXPANDER (widget);
826
827 gtk_widget_measure (widget: expander->box,
828 orientation,
829 for_size,
830 minimum, natural,
831 minimum_baseline, natural_baseline);
832}
833
834/**
835 * gtk_expander_new:
836 * @label: (nullable): the text of the label
837 *
838 * Creates a new expander using @label as the text of the label.
839 *
840 * Returns: a new `GtkExpander` widget.
841 */
842GtkWidget *
843gtk_expander_new (const char *label)
844{
845 return g_object_new (GTK_TYPE_EXPANDER, first_property_name: "label", label, NULL);
846}
847
848/**
849 * gtk_expander_new_with_mnemonic:
850 * @label: (nullable): the text of the label with an underscore
851 * in front of the mnemonic character
852 *
853 * Creates a new expander using @label as the text of the label.
854 *
855 * If characters in @label are preceded by an underscore, they are
856 * underlined. If you need a literal underscore character in a label,
857 * use “__” (two underscores). The first underlined character represents
858 * a keyboard accelerator called a mnemonic.
859 *
860 * Pressing Alt and that key activates the button.
861 *
862 * Returns: a new `GtkExpander` widget.
863 */
864GtkWidget *
865gtk_expander_new_with_mnemonic (const char *label)
866{
867 return g_object_new (GTK_TYPE_EXPANDER,
868 first_property_name: "label", label,
869 "use-underline", TRUE,
870 NULL);
871}
872
873/**
874 * gtk_expander_set_expanded: (attributes org.gtk.Method.set_property=expanded)
875 * @expander: a `GtkExpander`
876 * @expanded: whether the child widget is revealed
877 *
878 * Sets the state of the expander.
879 *
880 * Set to %TRUE, if you want the child widget to be revealed,
881 * and %FALSE if you want the child widget to be hidden.
882 */
883void
884gtk_expander_set_expanded (GtkExpander *expander,
885 gboolean expanded)
886{
887 GtkWidget *child;
888
889 g_return_if_fail (GTK_IS_EXPANDER (expander));
890
891 expanded = expanded != FALSE;
892
893 if (expander->expanded == expanded)
894 return;
895
896 expander->expanded = expanded;
897
898 if (expander->expanded)
899 gtk_widget_set_state_flags (widget: expander->arrow_widget, flags: GTK_STATE_FLAG_CHECKED, FALSE);
900 else
901 gtk_widget_unset_state_flags (widget: expander->arrow_widget, flags: GTK_STATE_FLAG_CHECKED);
902
903 child = expander->child;
904
905 if (child)
906 {
907 /* Transfer the ownership of the child to the box when
908 * expanded is set, and then back to us when it is unset
909 */
910 if (expander->expanded)
911 {
912 gtk_box_append (GTK_BOX (expander->box), child);
913 g_object_unref (object: expander->child);
914 }
915 else
916 {
917 g_object_ref (expander->child);
918 gtk_box_remove (GTK_BOX (expander->box), child);
919 }
920 gtk_expander_resize_toplevel (expander);
921 }
922
923 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: expander),
924 first_state: GTK_ACCESSIBLE_STATE_EXPANDED, expanded,
925 -1);
926
927 g_object_notify (G_OBJECT (expander), property_name: "expanded");
928}
929
930/**
931 * gtk_expander_get_expanded: (attributes org.gtk.Method.get_property=expanded)
932 * @expander:a `GtkExpander`
933 *
934 * Queries a `GtkExpander` and returns its current state.
935 *
936 * Returns %TRUE if the child widget is revealed.
937 *
938 * Returns: the current state of the expander
939 */
940gboolean
941gtk_expander_get_expanded (GtkExpander *expander)
942{
943 g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
944
945 return expander->expanded;
946}
947
948/**
949 * gtk_expander_set_label: (attributes org.gtk.Method.set_property=label)
950 * @expander: a `GtkExpander`
951 * @label: (nullable): a string
952 *
953 * Sets the text of the label of the expander to @label.
954 *
955 * This will also clear any previously set labels.
956 */
957void
958gtk_expander_set_label (GtkExpander *expander,
959 const char *label)
960{
961 g_return_if_fail (GTK_IS_EXPANDER (expander));
962
963 if (!label)
964 {
965 gtk_expander_set_label_widget (expander, NULL);
966 }
967 else
968 {
969 GtkWidget *child;
970
971 child = gtk_label_new (str: label);
972 gtk_label_set_use_underline (GTK_LABEL (child), setting: expander->use_underline);
973 gtk_label_set_use_markup (GTK_LABEL (child), setting: expander->use_markup);
974 gtk_widget_show (widget: child);
975
976 gtk_expander_set_label_widget (expander, label_widget: child);
977 }
978
979 g_object_notify (G_OBJECT (expander), property_name: "label");
980}
981
982/**
983 * gtk_expander_get_label: (attributes org.gtk.Method.get_property=label)
984 * @expander: a `GtkExpander`
985 *
986 * Fetches the text from a label widget.
987 *
988 * This is including any embedded underlines indicating mnemonics and
989 * Pango markup, as set by [method@Gtk.Expander.set_label]. If the label
990 * text has not been set the return value will be %NULL. This will be the
991 * case if you create an empty button with gtk_button_new() to use as a
992 * container.
993 *
994 * Returns: (nullable): The text of the label widget. This string is owned
995 * by the widget and must not be modified or freed.
996 */
997const char *
998gtk_expander_get_label (GtkExpander *expander)
999{
1000 g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1001
1002 if (GTK_IS_LABEL (expander->label_widget))
1003 return gtk_label_get_label (GTK_LABEL (expander->label_widget));
1004 else
1005 return NULL;
1006}
1007
1008/**
1009 * gtk_expander_set_use_underline: (attributes org.gtk.Method.set_property=use-underline)
1010 * @expander: a `GtkExpander`
1011 * @use_underline: %TRUE if underlines in the text indicate mnemonics
1012 *
1013 * If true, an underline in the text indicates a mnemonic.
1014 */
1015void
1016gtk_expander_set_use_underline (GtkExpander *expander,
1017 gboolean use_underline)
1018{
1019 g_return_if_fail (GTK_IS_EXPANDER (expander));
1020
1021 use_underline = use_underline != FALSE;
1022
1023 if (expander->use_underline != use_underline)
1024 {
1025 expander->use_underline = use_underline;
1026
1027 if (GTK_IS_LABEL (expander->label_widget))
1028 gtk_label_set_use_underline (GTK_LABEL (expander->label_widget), setting: use_underline);
1029
1030 g_object_notify (G_OBJECT (expander), property_name: "use-underline");
1031 }
1032}
1033
1034/**
1035 * gtk_expander_get_use_underline: (attributes org.gtk.Method.get_property=use-underline)
1036 * @expander: a `GtkExpander`
1037 *
1038 * Returns whether an underline in the text indicates a mnemonic.
1039 *
1040 * Returns: %TRUE if an embedded underline in the expander
1041 * label indicates the mnemonic accelerator keys
1042 */
1043gboolean
1044gtk_expander_get_use_underline (GtkExpander *expander)
1045{
1046 g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1047
1048 return expander->use_underline;
1049}
1050
1051/**
1052 * gtk_expander_set_use_markup: (attributes org.gtk.Method.set_property=use-markup)
1053 * @expander: a `GtkExpander`
1054 * @use_markup: %TRUE if the label’s text should be parsed for markup
1055 *
1056 * Sets whether the text of the label contains Pango markup.
1057 */
1058void
1059gtk_expander_set_use_markup (GtkExpander *expander,
1060 gboolean use_markup)
1061{
1062 g_return_if_fail (GTK_IS_EXPANDER (expander));
1063
1064 use_markup = use_markup != FALSE;
1065
1066 if (expander->use_markup != use_markup)
1067 {
1068 expander->use_markup = use_markup;
1069
1070 if (GTK_IS_LABEL (expander->label_widget))
1071 gtk_label_set_use_markup (GTK_LABEL (expander->label_widget), setting: use_markup);
1072
1073 g_object_notify (G_OBJECT (expander), property_name: "use-markup");
1074 }
1075}
1076
1077/**
1078 * gtk_expander_get_use_markup: (attributes org.gtk.Method.get_property=use-markup)
1079 * @expander: a `GtkExpander`
1080 *
1081 * Returns whether the label’s text is interpreted as Pango markup.
1082 *
1083 * Returns: %TRUE if the label’s text will be parsed for markup
1084 */
1085gboolean
1086gtk_expander_get_use_markup (GtkExpander *expander)
1087{
1088 g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1089
1090 return expander->use_markup;
1091}
1092
1093/**
1094 * gtk_expander_set_label_widget: (attributes org.gtk.Method.set_property=label-widget)
1095 * @expander: a `GtkExpander`
1096 * @label_widget: (nullable): the new label widget
1097 *
1098 * Set the label widget for the expander.
1099 *
1100 * This is the widget that will appear embedded alongside
1101 * the expander arrow.
1102 */
1103void
1104gtk_expander_set_label_widget (GtkExpander *expander,
1105 GtkWidget *label_widget)
1106{
1107 GtkWidget *widget;
1108
1109 g_return_if_fail (GTK_IS_EXPANDER (expander));
1110 g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
1111 g_return_if_fail (label_widget == NULL || gtk_widget_get_parent (label_widget) == NULL);
1112
1113 if (expander->label_widget == label_widget)
1114 return;
1115
1116 if (expander->label_widget)
1117 gtk_box_remove (GTK_BOX (expander->title_widget), child: expander->label_widget);
1118
1119 expander->label_widget = label_widget;
1120 widget = GTK_WIDGET (expander);
1121
1122 if (label_widget)
1123 {
1124 expander->label_widget = label_widget;
1125
1126 gtk_box_append (GTK_BOX (expander->title_widget), child: label_widget);
1127 }
1128
1129 if (gtk_widget_get_visible (widget))
1130 gtk_widget_queue_resize (widget);
1131
1132 g_object_freeze_notify (G_OBJECT (expander));
1133 g_object_notify (G_OBJECT (expander), property_name: "label-widget");
1134 g_object_notify (G_OBJECT (expander), property_name: "label");
1135 g_object_thaw_notify (G_OBJECT (expander));
1136}
1137
1138/**
1139 * gtk_expander_get_label_widget: (attributes org.gtk.Method.get_property=label-widget)
1140 * @expander: a `GtkExpander`
1141 *
1142 * Retrieves the label widget for the frame.
1143 *
1144 * Returns: (nullable) (transfer none): the label widget
1145 */
1146GtkWidget *
1147gtk_expander_get_label_widget (GtkExpander *expander)
1148{
1149 g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1150
1151 return expander->label_widget;
1152}
1153
1154/**
1155 * gtk_expander_set_resize_toplevel: (attributes org.gtk.Method.set_property=resize-toplevel)
1156 * @expander: a `GtkExpander`
1157 * @resize_toplevel: whether to resize the toplevel
1158 *
1159 * Sets whether the expander will resize the toplevel widget
1160 * containing the expander upon resizing and collpasing.
1161 */
1162void
1163gtk_expander_set_resize_toplevel (GtkExpander *expander,
1164 gboolean resize_toplevel)
1165{
1166 g_return_if_fail (GTK_IS_EXPANDER (expander));
1167
1168 if (expander->resize_toplevel != resize_toplevel)
1169 {
1170 expander->resize_toplevel = resize_toplevel ? TRUE : FALSE;
1171 g_object_notify (G_OBJECT (expander), property_name: "resize-toplevel");
1172 }
1173}
1174
1175/**
1176 * gtk_expander_get_resize_toplevel: (attributes org.gtk.Method.get_property=resize-toplevel)
1177 * @expander: a `GtkExpander`
1178 *
1179 * Returns whether the expander will resize the toplevel widget
1180 * containing the expander upon resizing and collpasing.
1181 *
1182 * Returns: the “resize toplevel” setting.
1183 */
1184gboolean
1185gtk_expander_get_resize_toplevel (GtkExpander *expander)
1186{
1187 g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1188
1189 return expander->resize_toplevel;
1190}
1191
1192/**
1193 * gtk_expander_set_child: (attributes org.gtk.Method.set_property=child)
1194 * @expander: a `GtkExpander`
1195 * @child: (nullable): the child widget
1196 *
1197 * Sets the child widget of @expander.
1198 */
1199void
1200gtk_expander_set_child (GtkExpander *expander,
1201 GtkWidget *child)
1202{
1203 g_return_if_fail (GTK_IS_EXPANDER (expander));
1204 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
1205
1206 if (expander->child == child)
1207 return;
1208
1209 if (expander->child)
1210 {
1211 if (!expander->expanded)
1212 g_object_unref (object: expander->child);
1213 else
1214 gtk_box_remove (GTK_BOX (expander->box), child: expander->child);
1215 }
1216
1217 expander->child = child;
1218
1219 if (expander->child)
1220 {
1221 /* We only add the child to the box if the expander is
1222 * expanded; otherwise we just claim ownership of the
1223 * child by sinking its floating reference, or acquiring
1224 * an additional reference to it. The reference will be
1225 * dropped once the expander is expanded
1226 */
1227 if (expander->expanded)
1228 gtk_box_append (GTK_BOX (expander->box), child: expander->child);
1229 else
1230 g_object_ref_sink (expander->child);
1231
1232 gtk_accessible_update_relation (self: GTK_ACCESSIBLE (ptr: expander),
1233 first_relation: GTK_ACCESSIBLE_RELATION_CONTROLS, expander->child, NULL,
1234 -1);
1235 }
1236 else
1237 {
1238 gtk_accessible_reset_relation (self: GTK_ACCESSIBLE (ptr: expander),
1239 relation: GTK_ACCESSIBLE_RELATION_CONTROLS);
1240 }
1241
1242 g_object_notify (G_OBJECT (expander), property_name: "child");
1243}
1244
1245/**
1246 * gtk_expander_get_child: (attributes org.gtk.Method.get_property=child)
1247 * @expander: a `GtkExpander`
1248 *
1249 * Gets the child widget of @expander.
1250 *
1251 * Returns: (nullable) (transfer none): the child widget of @expander
1252 */
1253GtkWidget *
1254gtk_expander_get_child (GtkExpander *expander)
1255{
1256 g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1257
1258 return expander->child;
1259}
1260
1261

source code of gtk/gtk/gtkexpander.c