1 /* GTK - The GIMP Toolkit
3 * Copyright (C) 2003 Sun Microsystems, Inc.
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.
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.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
21 * Mark McLoughlin <mark@skynet.ie>
26 #include "gtkexpander.h"
29 #include "gtkbuildable.h"
30 #include "gtkcontainer.h"
31 #include "gtkmarshalers.h"
34 #include "gtkprivate.h"
35 #include <gdk/gdkkeysyms.h>
38 #define GTK_EXPANDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_EXPANDER, GtkExpanderPrivate))
40 #define DEFAULT_EXPANDER_SIZE 10
41 #define DEFAULT_EXPANDER_SPACING 2
54 struct _GtkExpanderPrivate
56 GtkWidget *label_widget;
57 GdkWindow *event_window;
60 GtkExpanderStyle expander_style;
61 guint animation_timeout;
65 guint use_underline : 1;
67 guint button_down : 1;
71 static void gtk_expander_set_property (GObject *object,
75 static void gtk_expander_get_property (GObject *object,
80 static void gtk_expander_destroy (GtkObject *object);
82 static void gtk_expander_realize (GtkWidget *widget);
83 static void gtk_expander_unrealize (GtkWidget *widget);
84 static void gtk_expander_size_request (GtkWidget *widget,
85 GtkRequisition *requisition);
86 static void gtk_expander_size_allocate (GtkWidget *widget,
87 GtkAllocation *allocation);
88 static void gtk_expander_map (GtkWidget *widget);
89 static void gtk_expander_unmap (GtkWidget *widget);
90 static gboolean gtk_expander_expose (GtkWidget *widget,
91 GdkEventExpose *event);
92 static gboolean gtk_expander_button_press (GtkWidget *widget,
93 GdkEventButton *event);
94 static gboolean gtk_expander_button_release (GtkWidget *widget,
95 GdkEventButton *event);
96 static gboolean gtk_expander_enter_notify (GtkWidget *widget,
97 GdkEventCrossing *event);
98 static gboolean gtk_expander_leave_notify (GtkWidget *widget,
99 GdkEventCrossing *event);
100 static gboolean gtk_expander_focus (GtkWidget *widget,
101 GtkDirectionType direction);
102 static void gtk_expander_grab_notify (GtkWidget *widget,
103 gboolean was_grabbed);
104 static void gtk_expander_state_changed (GtkWidget *widget,
105 GtkStateType previous_state);
106 static gboolean gtk_expander_drag_motion (GtkWidget *widget,
107 GdkDragContext *context,
111 static void gtk_expander_drag_leave (GtkWidget *widget,
112 GdkDragContext *context,
115 static void gtk_expander_add (GtkContainer *container,
117 static void gtk_expander_remove (GtkContainer *container,
119 static void gtk_expander_forall (GtkContainer *container,
120 gboolean include_internals,
121 GtkCallback callback,
122 gpointer callback_data);
124 static void gtk_expander_activate (GtkExpander *expander);
126 static void get_expander_bounds (GtkExpander *expander,
130 static void gtk_expander_buildable_init (GtkBuildableIface *iface);
131 static void gtk_expander_buildable_add_child (GtkBuildable *buildable,
136 G_DEFINE_TYPE_WITH_CODE (GtkExpander, gtk_expander, GTK_TYPE_BIN,
137 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
138 gtk_expander_buildable_init))
141 gtk_expander_class_init (GtkExpanderClass *klass)
143 GObjectClass *gobject_class;
144 GtkObjectClass *object_class;
145 GtkWidgetClass *widget_class;
146 GtkContainerClass *container_class;
148 gobject_class = (GObjectClass *) klass;
149 object_class = (GtkObjectClass *) klass;
150 widget_class = (GtkWidgetClass *) klass;
151 container_class = (GtkContainerClass *) klass;
153 gobject_class->set_property = gtk_expander_set_property;
154 gobject_class->get_property = gtk_expander_get_property;
156 object_class->destroy = gtk_expander_destroy;
158 widget_class->realize = gtk_expander_realize;
159 widget_class->unrealize = gtk_expander_unrealize;
160 widget_class->size_request = gtk_expander_size_request;
161 widget_class->size_allocate = gtk_expander_size_allocate;
162 widget_class->map = gtk_expander_map;
163 widget_class->unmap = gtk_expander_unmap;
164 widget_class->expose_event = gtk_expander_expose;
165 widget_class->button_press_event = gtk_expander_button_press;
166 widget_class->button_release_event = gtk_expander_button_release;
167 widget_class->enter_notify_event = gtk_expander_enter_notify;
168 widget_class->leave_notify_event = gtk_expander_leave_notify;
169 widget_class->focus = gtk_expander_focus;
170 widget_class->grab_notify = gtk_expander_grab_notify;
171 widget_class->state_changed = gtk_expander_state_changed;
172 widget_class->drag_motion = gtk_expander_drag_motion;
173 widget_class->drag_leave = gtk_expander_drag_leave;
175 container_class->add = gtk_expander_add;
176 container_class->remove = gtk_expander_remove;
177 container_class->forall = gtk_expander_forall;
179 klass->activate = gtk_expander_activate;
181 g_type_class_add_private (klass, sizeof (GtkExpanderPrivate));
183 g_object_class_install_property (gobject_class,
185 g_param_spec_boolean ("expanded",
187 P_("Whether the expander has been opened to reveal the child widget"),
189 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
191 g_object_class_install_property (gobject_class,
193 g_param_spec_string ("label",
195 P_("Text of the expander's label"),
197 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
199 g_object_class_install_property (gobject_class,
201 g_param_spec_boolean ("use-underline",
203 P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
205 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
207 g_object_class_install_property (gobject_class,
209 g_param_spec_boolean ("use-markup",
211 P_("The text of the label includes XML markup. See pango_parse_markup()"),
213 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
215 g_object_class_install_property (gobject_class,
217 g_param_spec_int ("spacing",
219 P_("Space to put between the label and the child"),
223 GTK_PARAM_READWRITE));
225 g_object_class_install_property (gobject_class,
227 g_param_spec_object ("label-widget",
229 P_("A widget to display in place of the usual expander label"),
231 GTK_PARAM_READWRITE));
233 gtk_widget_class_install_style_property (widget_class,
234 g_param_spec_int ("expander-size",
236 P_("Size of the expander arrow"),
239 DEFAULT_EXPANDER_SIZE,
240 GTK_PARAM_READABLE));
242 gtk_widget_class_install_style_property (widget_class,
243 g_param_spec_int ("expander-spacing",
244 P_("Indicator Spacing"),
245 P_("Spacing around expander arrow"),
248 DEFAULT_EXPANDER_SPACING,
249 GTK_PARAM_READABLE));
251 widget_class->activate_signal =
252 g_signal_new (I_("activate"),
253 G_TYPE_FROM_CLASS (gobject_class),
254 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
255 G_STRUCT_OFFSET (GtkExpanderClass, activate),
257 _gtk_marshal_VOID__VOID,
262 gtk_expander_init (GtkExpander *expander)
264 GtkExpanderPrivate *priv;
266 expander->priv = priv = GTK_EXPANDER_GET_PRIVATE (expander);
268 gtk_widget_set_can_focus (GTK_WIDGET (expander), TRUE);
269 gtk_widget_set_has_window (GTK_WIDGET (expander), FALSE);
271 priv->label_widget = NULL;
272 priv->event_window = NULL;
275 priv->expander_style = GTK_EXPANDER_COLLAPSED;
276 priv->animation_timeout = 0;
278 priv->expanded = FALSE;
279 priv->use_underline = FALSE;
280 priv->use_markup = FALSE;
281 priv->button_down = FALSE;
282 priv->prelight = FALSE;
283 priv->expand_timer = 0;
285 gtk_drag_dest_set (GTK_WIDGET (expander), 0, NULL, 0, 0);
286 gtk_drag_dest_set_track_motion (GTK_WIDGET (expander), TRUE);
290 gtk_expander_buildable_add_child (GtkBuildable *buildable,
296 gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
297 else if (strcmp (type, "label") == 0)
298 gtk_expander_set_label_widget (GTK_EXPANDER (buildable), GTK_WIDGET (child));
300 GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GTK_EXPANDER (buildable), type);
304 gtk_expander_buildable_init (GtkBuildableIface *iface)
306 iface->add_child = gtk_expander_buildable_add_child;
310 gtk_expander_set_property (GObject *object,
315 GtkExpander *expander = GTK_EXPANDER (object);
320 gtk_expander_set_expanded (expander, g_value_get_boolean (value));
323 gtk_expander_set_label (expander, g_value_get_string (value));
325 case PROP_USE_UNDERLINE:
326 gtk_expander_set_use_underline (expander, g_value_get_boolean (value));
328 case PROP_USE_MARKUP:
329 gtk_expander_set_use_markup (expander, g_value_get_boolean (value));
332 gtk_expander_set_spacing (expander, g_value_get_int (value));
334 case PROP_LABEL_WIDGET:
335 gtk_expander_set_label_widget (expander, g_value_get_object (value));
338 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
344 gtk_expander_get_property (GObject *object,
349 GtkExpander *expander = GTK_EXPANDER (object);
350 GtkExpanderPrivate *priv = expander->priv;
355 g_value_set_boolean (value, priv->expanded);
358 g_value_set_string (value, gtk_expander_get_label (expander));
360 case PROP_USE_UNDERLINE:
361 g_value_set_boolean (value, priv->use_underline);
363 case PROP_USE_MARKUP:
364 g_value_set_boolean (value, priv->use_markup);
367 g_value_set_int (value, priv->spacing);
369 case PROP_LABEL_WIDGET:
370 g_value_set_object (value,
372 G_OBJECT (priv->label_widget) : NULL);
375 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
381 gtk_expander_destroy (GtkObject *object)
383 GtkExpanderPrivate *priv = GTK_EXPANDER (object)->priv;
385 if (priv->animation_timeout)
387 g_source_remove (priv->animation_timeout);
388 priv->animation_timeout = 0;
391 GTK_OBJECT_CLASS (gtk_expander_parent_class)->destroy (object);
395 gtk_expander_realize (GtkWidget *widget)
397 GtkExpanderPrivate *priv;
398 GdkWindowAttr attributes;
399 gint attributes_mask;
401 GdkRectangle expander_rect;
404 priv = GTK_EXPANDER (widget)->priv;
405 gtk_widget_set_realized (widget, TRUE);
407 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
409 get_expander_bounds (GTK_EXPANDER (widget), &expander_rect);
411 if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
413 GtkRequisition label_requisition;
415 gtk_widget_get_child_requisition (priv->label_widget, &label_requisition);
416 label_height = label_requisition.height;
421 attributes.window_type = GDK_WINDOW_CHILD;
422 attributes.x = widget->allocation.x + border_width;
423 attributes.y = widget->allocation.y + border_width;
424 attributes.width = MAX (widget->allocation.width - 2 * border_width, 1);
425 attributes.height = MAX (expander_rect.height, label_height - 2 * border_width);
426 attributes.wclass = GDK_INPUT_ONLY;
427 attributes.event_mask = gtk_widget_get_events (widget) |
428 GDK_BUTTON_PRESS_MASK |
429 GDK_BUTTON_RELEASE_MASK |
430 GDK_ENTER_NOTIFY_MASK |
431 GDK_LEAVE_NOTIFY_MASK;
433 attributes_mask = GDK_WA_X | GDK_WA_Y;
435 widget->window = gtk_widget_get_parent_window (widget);
436 g_object_ref (widget->window);
438 priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
439 &attributes, attributes_mask);
440 gdk_window_set_user_data (priv->event_window, widget);
442 widget->style = gtk_style_attach (widget->style, widget->window);
446 gtk_expander_unrealize (GtkWidget *widget)
448 GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
450 if (priv->event_window)
452 gdk_window_set_user_data (priv->event_window, NULL);
453 gdk_window_destroy (priv->event_window);
454 priv->event_window = NULL;
457 GTK_WIDGET_CLASS (gtk_expander_parent_class)->unrealize (widget);
461 gtk_expander_size_request (GtkWidget *widget,
462 GtkRequisition *requisition)
464 GtkExpander *expander;
466 GtkExpanderPrivate *priv;
469 gint expander_spacing;
470 gboolean interior_focus;
474 bin = GTK_BIN (widget);
475 expander = GTK_EXPANDER (widget);
476 priv = expander->priv;
478 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
480 gtk_widget_style_get (widget,
481 "interior-focus", &interior_focus,
482 "focus-line-width", &focus_width,
483 "focus-padding", &focus_pad,
484 "expander-size", &expander_size,
485 "expander-spacing", &expander_spacing,
488 requisition->width = expander_size + 2 * expander_spacing +
489 2 * focus_width + 2 * focus_pad;
490 requisition->height = interior_focus ? (2 * focus_width + 2 * focus_pad) : 0;
492 if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
494 GtkRequisition label_requisition;
496 gtk_widget_size_request (priv->label_widget, &label_requisition);
498 requisition->width += label_requisition.width;
499 requisition->height += label_requisition.height;
502 requisition->height = MAX (expander_size + 2 * expander_spacing, requisition->height);
505 requisition->height += 2 * focus_width + 2 * focus_pad;
507 if (bin->child && GTK_WIDGET_CHILD_VISIBLE (bin->child))
509 GtkRequisition child_requisition;
511 gtk_widget_size_request (bin->child, &child_requisition);
513 requisition->width = MAX (requisition->width, child_requisition.width);
514 requisition->height += child_requisition.height + priv->spacing;
517 requisition->width += 2 * border_width;
518 requisition->height += 2 * border_width;
522 get_expander_bounds (GtkExpander *expander,
526 GtkExpanderPrivate *priv;
529 gint expander_spacing;
530 gboolean interior_focus;
535 widget = GTK_WIDGET (expander);
536 priv = expander->priv;
538 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
540 gtk_widget_style_get (widget,
541 "interior-focus", &interior_focus,
542 "focus-line-width", &focus_width,
543 "focus-padding", &focus_pad,
544 "expander-size", &expander_size,
545 "expander-spacing", &expander_spacing,
548 ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
550 rect->x = widget->allocation.x + border_width;
551 rect->y = widget->allocation.y + border_width;
554 rect->x += expander_spacing;
556 rect->x += widget->allocation.width - 2 * border_width -
557 expander_spacing - expander_size;
559 if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
561 GtkAllocation label_allocation;
563 label_allocation = priv->label_widget->allocation;
565 if (expander_size < label_allocation.height)
566 rect->y += focus_width + focus_pad + (label_allocation.height - expander_size) / 2;
568 rect->y += expander_spacing;
572 rect->y += expander_spacing;
578 rect->x += focus_width + focus_pad;
580 rect->x -= focus_width + focus_pad;
581 rect->y += focus_width + focus_pad;
584 rect->width = rect->height = expander_size;
588 gtk_expander_size_allocate (GtkWidget *widget,
589 GtkAllocation *allocation)
591 GtkExpander *expander;
593 GtkExpanderPrivate *priv;
594 GtkRequisition child_requisition;
595 gboolean child_visible = FALSE;
598 gint expander_spacing;
599 gboolean interior_focus;
604 expander = GTK_EXPANDER (widget);
605 bin = GTK_BIN (widget);
606 priv = expander->priv;
608 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
610 gtk_widget_style_get (widget,
611 "interior-focus", &interior_focus,
612 "focus-line-width", &focus_width,
613 "focus-padding", &focus_pad,
614 "expander-size", &expander_size,
615 "expander-spacing", &expander_spacing,
618 child_requisition.width = 0;
619 child_requisition.height = 0;
620 if (bin->child && GTK_WIDGET_CHILD_VISIBLE (bin->child))
622 child_visible = TRUE;
623 gtk_widget_get_child_requisition (bin->child, &child_requisition);
626 widget->allocation = *allocation;
628 if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
630 GtkAllocation label_allocation;
631 GtkRequisition label_requisition;
634 gtk_widget_get_child_requisition (priv->label_widget, &label_requisition);
636 ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
639 label_allocation.x = (widget->allocation.x +
640 border_width + focus_width + focus_pad +
641 expander_size + 2 * expander_spacing);
643 label_allocation.x = (widget->allocation.x + widget->allocation.width -
644 (label_requisition.width +
645 border_width + focus_width + focus_pad +
646 expander_size + 2 * expander_spacing));
648 label_allocation.y = widget->allocation.y + border_width + focus_width + focus_pad;
650 label_allocation.width = MIN (label_requisition.width,
651 allocation->width - 2 * border_width -
652 expander_size - 2 * expander_spacing -
653 2 * focus_width - 2 * focus_pad);
654 label_allocation.width = MAX (label_allocation.width, 1);
656 label_allocation.height = MIN (label_requisition.height,
657 allocation->height - 2 * border_width -
658 2 * focus_width - 2 * focus_pad -
659 (child_visible ? priv->spacing : 0));
660 label_allocation.height = MAX (label_allocation.height, 1);
662 gtk_widget_size_allocate (priv->label_widget, &label_allocation);
664 label_height = label_allocation.height;
671 if (gtk_widget_get_realized (widget))
675 get_expander_bounds (expander, &rect);
677 gdk_window_move_resize (priv->event_window,
678 allocation->x + border_width,
679 allocation->y + border_width,
680 MAX (allocation->width - 2 * border_width, 1),
681 MAX (rect.height, label_height - 2 * border_width));
686 GtkAllocation child_allocation;
689 top_height = MAX (2 * expander_spacing + expander_size,
691 (interior_focus ? 2 * focus_width + 2 * focus_pad : 0));
693 child_allocation.x = widget->allocation.x + border_width;
694 child_allocation.y = widget->allocation.y + border_width + top_height + priv->spacing;
697 child_allocation.y += 2 * focus_width + 2 * focus_pad;
699 child_allocation.width = MAX (allocation->width - 2 * border_width, 1);
701 child_allocation.height = allocation->height - top_height -
702 2 * border_width - priv->spacing -
703 (!interior_focus ? 2 * focus_width + 2 * focus_pad : 0);
704 child_allocation.height = MAX (child_allocation.height, 1);
706 gtk_widget_size_allocate (bin->child, &child_allocation);
711 gtk_expander_map (GtkWidget *widget)
713 GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
715 if (priv->label_widget)
716 gtk_widget_map (priv->label_widget);
718 GTK_WIDGET_CLASS (gtk_expander_parent_class)->map (widget);
720 if (priv->event_window)
721 gdk_window_show (priv->event_window);
725 gtk_expander_unmap (GtkWidget *widget)
727 GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
729 if (priv->event_window)
730 gdk_window_hide (priv->event_window);
732 GTK_WIDGET_CLASS (gtk_expander_parent_class)->unmap (widget);
734 if (priv->label_widget)
735 gtk_widget_unmap (priv->label_widget);
739 gtk_expander_paint_prelight (GtkExpander *expander)
742 GtkContainer *container;
743 GtkExpanderPrivate *priv;
745 gboolean interior_focus;
749 int expander_spacing;
752 priv = expander->priv;
753 widget = GTK_WIDGET (expander);
754 container = GTK_CONTAINER (expander);
756 gtk_widget_style_get (widget,
757 "interior-focus", &interior_focus,
758 "focus-line-width", &focus_width,
759 "focus-padding", &focus_pad,
760 "expander-size", &expander_size,
761 "expander-spacing", &expander_spacing,
764 border_width = gtk_container_get_border_width (container);
765 area.x = widget->allocation.x + border_width;
766 area.y = widget->allocation.y + border_width;
767 area.width = widget->allocation.width - (2 * border_width);
769 if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
770 area.height = priv->label_widget->allocation.height;
774 area.height += interior_focus ? (focus_width + focus_pad) * 2 : 0;
775 area.height = MAX (area.height, expander_size + 2 * expander_spacing);
776 area.height += !interior_focus ? (focus_width + focus_pad) * 2 : 0;
778 gtk_paint_flat_box (widget->style, widget->window,
780 GTK_SHADOW_ETCHED_OUT,
781 &area, widget, "expander",
783 area.width, area.height);
787 gtk_expander_paint (GtkExpander *expander)
793 widget = GTK_WIDGET (expander);
795 get_expander_bounds (expander, &clip);
797 state = widget->state;
798 if (expander->priv->prelight)
800 state = GTK_STATE_PRELIGHT;
802 gtk_expander_paint_prelight (expander);
805 gtk_paint_expander (widget->style,
811 clip.x + clip.width / 2,
812 clip.y + clip.height / 2,
813 expander->priv->expander_style);
817 gtk_expander_paint_focus (GtkExpander *expander,
821 GtkExpanderPrivate *priv;
823 gint x, y, width, height;
824 gboolean interior_focus;
829 gint expander_spacing;
832 widget = GTK_WIDGET (expander);
833 priv = expander->priv;
835 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
837 gtk_widget_style_get (widget,
838 "interior-focus", &interior_focus,
839 "focus-line-width", &focus_width,
840 "focus-padding", &focus_pad,
841 "expander-size", &expander_size,
842 "expander-spacing", &expander_spacing,
845 ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
849 if (priv->label_widget)
851 if (gtk_widget_get_visible (priv->label_widget))
853 GtkAllocation label_allocation = priv->label_widget->allocation;
855 width = label_allocation.width;
856 height = label_allocation.height;
859 width += 2 * focus_pad + 2 * focus_width;
860 height += 2 * focus_pad + 2 * focus_width;
862 x = widget->allocation.x + border_width;
863 y = widget->allocation.y + border_width;
868 x += expander_spacing * 2 + expander_size;
872 x += widget->allocation.width - 2 * border_width
873 - expander_spacing * 2 - expander_size - width;
878 width += expander_size + 2 * expander_spacing;
879 height = MAX (height, expander_size + 2 * expander_spacing);
884 get_expander_bounds (expander, &rect);
886 x = rect.x - focus_pad;
887 y = rect.y - focus_pad;
888 width = rect.width + 2 * focus_pad;
889 height = rect.height + 2 * focus_pad;
892 gtk_paint_focus (widget->style, widget->window, gtk_widget_get_state (widget),
893 area, widget, "expander",
894 x, y, width, height);
898 gtk_expander_expose (GtkWidget *widget,
899 GdkEventExpose *event)
901 if (gtk_widget_is_drawable (widget))
903 GtkExpander *expander = GTK_EXPANDER (widget);
905 gtk_expander_paint (expander);
907 if (gtk_widget_has_focus (widget))
908 gtk_expander_paint_focus (expander, &event->area);
910 GTK_WIDGET_CLASS (gtk_expander_parent_class)->expose_event (widget, event);
917 gtk_expander_button_press (GtkWidget *widget,
918 GdkEventButton *event)
920 GtkExpander *expander = GTK_EXPANDER (widget);
922 if (event->button == 1 && event->window == expander->priv->event_window)
924 expander->priv->button_down = TRUE;
932 gtk_expander_button_release (GtkWidget *widget,
933 GdkEventButton *event)
935 GtkExpander *expander = GTK_EXPANDER (widget);
937 if (event->button == 1 && expander->priv->button_down)
939 gtk_widget_activate (widget);
940 expander->priv->button_down = FALSE;
948 gtk_expander_grab_notify (GtkWidget *widget,
949 gboolean was_grabbed)
952 GTK_EXPANDER (widget)->priv->button_down = FALSE;
956 gtk_expander_state_changed (GtkWidget *widget,
957 GtkStateType previous_state)
959 if (!gtk_widget_is_sensitive (widget))
960 GTK_EXPANDER (widget)->priv->button_down = FALSE;
964 gtk_expander_redraw_expander (GtkExpander *expander)
968 widget = GTK_WIDGET (expander);
970 if (gtk_widget_get_realized (widget))
971 gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE);
975 gtk_expander_enter_notify (GtkWidget *widget,
976 GdkEventCrossing *event)
978 GtkExpander *expander = GTK_EXPANDER (widget);
979 GtkWidget *event_widget;
981 event_widget = gtk_get_event_widget ((GdkEvent *) event);
983 if (event_widget == widget &&
984 event->detail != GDK_NOTIFY_INFERIOR)
986 expander->priv->prelight = TRUE;
988 if (expander->priv->label_widget)
989 gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_PRELIGHT);
991 gtk_expander_redraw_expander (expander);
998 gtk_expander_leave_notify (GtkWidget *widget,
999 GdkEventCrossing *event)
1001 GtkExpander *expander = GTK_EXPANDER (widget);
1002 GtkWidget *event_widget;
1004 event_widget = gtk_get_event_widget ((GdkEvent *) event);
1006 if (event_widget == widget &&
1007 event->detail != GDK_NOTIFY_INFERIOR)
1009 expander->priv->prelight = FALSE;
1011 if (expander->priv->label_widget)
1012 gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_NORMAL);
1014 gtk_expander_redraw_expander (expander);
1021 expand_timeout (gpointer data)
1023 GtkExpander *expander = GTK_EXPANDER (data);
1024 GtkExpanderPrivate *priv = expander->priv;
1026 priv->expand_timer = 0;
1027 gtk_expander_set_expanded (expander, TRUE);
1033 gtk_expander_drag_motion (GtkWidget *widget,
1034 GdkDragContext *context,
1039 GtkExpander *expander = GTK_EXPANDER (widget);
1040 GtkExpanderPrivate *priv = expander->priv;
1042 if (!priv->expanded && !priv->expand_timer)
1044 GtkSettings *settings;
1047 settings = gtk_widget_get_settings (widget);
1048 g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
1050 priv->expand_timer = gdk_threads_add_timeout (timeout, (GSourceFunc) expand_timeout, expander);
1057 gtk_expander_drag_leave (GtkWidget *widget,
1058 GdkDragContext *context,
1061 GtkExpander *expander = GTK_EXPANDER (widget);
1062 GtkExpanderPrivate *priv = expander->priv;
1064 if (priv->expand_timer)
1066 g_source_remove (priv->expand_timer);
1067 priv->expand_timer = 0;
1080 focus_current_site (GtkExpander *expander,
1081 GtkDirectionType direction)
1083 GtkWidget *current_focus;
1085 current_focus = gtk_container_get_focus_child (GTK_CONTAINER (expander));
1090 return gtk_widget_child_focus (current_focus, direction);
1094 focus_in_site (GtkExpander *expander,
1096 GtkDirectionType direction)
1101 gtk_widget_grab_focus (GTK_WIDGET (expander));
1104 if (expander->priv->label_widget)
1105 return gtk_widget_child_focus (expander->priv->label_widget, direction);
1110 GtkWidget *child = gtk_bin_get_child (GTK_BIN (expander));
1112 if (child && GTK_WIDGET_CHILD_VISIBLE (child))
1113 return gtk_widget_child_focus (child, direction);
1121 g_assert_not_reached ();
1126 get_next_site (GtkExpander *expander,
1128 GtkDirectionType direction)
1132 ltr = gtk_widget_get_direction (GTK_WIDGET (expander)) != GTK_TEXT_DIR_RTL;
1139 case GTK_DIR_TAB_BACKWARD:
1143 case GTK_DIR_TAB_FORWARD:
1146 return FOCUS_WIDGET;
1151 case GTK_DIR_TAB_BACKWARD:
1155 return ltr ? FOCUS_NONE : FOCUS_LABEL;
1156 case GTK_DIR_TAB_FORWARD:
1160 return ltr ? FOCUS_LABEL : FOCUS_NONE;
1166 case GTK_DIR_TAB_BACKWARD:
1168 return FOCUS_WIDGET;
1170 return ltr ? FOCUS_WIDGET : FOCUS_CHILD;
1171 case GTK_DIR_TAB_FORWARD:
1175 return ltr ? FOCUS_CHILD : FOCUS_WIDGET;
1181 case GTK_DIR_TAB_BACKWARD:
1185 case GTK_DIR_TAB_FORWARD:
1192 g_assert_not_reached ();
1197 gtk_expander_focus (GtkWidget *widget,
1198 GtkDirectionType direction)
1200 GtkExpander *expander = GTK_EXPANDER (widget);
1202 if (!focus_current_site (expander, direction))
1204 GtkWidget *old_focus_child;
1205 gboolean widget_is_focus;
1206 FocusSite site = FOCUS_NONE;
1208 widget_is_focus = gtk_widget_is_focus (widget);
1209 old_focus_child = gtk_container_get_focus_child (GTK_CONTAINER (widget));
1211 if (old_focus_child && old_focus_child == expander->priv->label_widget)
1213 else if (old_focus_child)
1215 else if (widget_is_focus)
1216 site = FOCUS_WIDGET;
1218 while ((site = get_next_site (expander, site, direction)) != FOCUS_NONE)
1220 if (focus_in_site (expander, site, direction))
1231 gtk_expander_add (GtkContainer *container,
1234 GTK_CONTAINER_CLASS (gtk_expander_parent_class)->add (container, widget);
1236 gtk_widget_set_child_visible (widget, GTK_EXPANDER (container)->priv->expanded);
1237 gtk_widget_queue_resize (GTK_WIDGET (container));
1241 gtk_expander_remove (GtkContainer *container,
1244 GtkExpander *expander = GTK_EXPANDER (container);
1246 if (GTK_EXPANDER (expander)->priv->label_widget == widget)
1247 gtk_expander_set_label_widget (expander, NULL);
1249 GTK_CONTAINER_CLASS (gtk_expander_parent_class)->remove (container, widget);
1253 gtk_expander_forall (GtkContainer *container,
1254 gboolean include_internals,
1255 GtkCallback callback,
1256 gpointer callback_data)
1258 GtkBin *bin = GTK_BIN (container);
1259 GtkExpanderPrivate *priv = GTK_EXPANDER (container)->priv;
1262 (* callback) (bin->child, callback_data);
1264 if (priv->label_widget)
1265 (* callback) (priv->label_widget, callback_data);
1269 gtk_expander_activate (GtkExpander *expander)
1271 gtk_expander_set_expanded (expander, !expander->priv->expanded);
1276 * @label: the text of the label
1278 * Creates a new expander using @label as the text of the label.
1280 * Return value: a new #GtkExpander widget.
1285 gtk_expander_new (const gchar *label)
1287 return g_object_new (GTK_TYPE_EXPANDER, "label", label, NULL);
1291 * gtk_expander_new_with_mnemonic:
1292 * @label: (allow-none): the text of the label with an underscore in front of the
1293 * mnemonic character
1295 * Creates a new expander using @label as the text of the label.
1296 * If characters in @label are preceded by an underscore, they are underlined.
1297 * If you need a literal underscore character in a label, use '__' (two
1298 * underscores). The first underlined character represents a keyboard
1299 * accelerator called a mnemonic.
1300 * Pressing Alt and that key activates the button.
1302 * Return value: a new #GtkExpander widget.
1307 gtk_expander_new_with_mnemonic (const gchar *label)
1309 return g_object_new (GTK_TYPE_EXPANDER,
1311 "use-underline", TRUE,
1316 gtk_expander_animation_timeout (GtkExpander *expander)
1318 GtkExpanderPrivate *priv = expander->priv;
1320 gboolean finish = FALSE;
1322 if (gtk_widget_get_realized (GTK_WIDGET (expander)))
1324 get_expander_bounds (expander, &area);
1325 gdk_window_invalidate_rect (GTK_WIDGET (expander)->window, &area, TRUE);
1330 if (priv->expander_style == GTK_EXPANDER_COLLAPSED)
1332 priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED;
1336 priv->expander_style = GTK_EXPANDER_EXPANDED;
1342 if (priv->expander_style == GTK_EXPANDER_EXPANDED)
1344 priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED;
1348 priv->expander_style = GTK_EXPANDER_COLLAPSED;
1355 priv->animation_timeout = 0;
1356 if (GTK_BIN (expander)->child)
1357 gtk_widget_set_child_visible (GTK_BIN (expander)->child, priv->expanded);
1358 gtk_widget_queue_resize (GTK_WIDGET (expander));
1365 gtk_expander_start_animation (GtkExpander *expander)
1367 GtkExpanderPrivate *priv = expander->priv;
1369 if (priv->animation_timeout)
1370 g_source_remove (priv->animation_timeout);
1372 priv->animation_timeout =
1373 gdk_threads_add_timeout (50,
1374 (GSourceFunc) gtk_expander_animation_timeout,
1379 * gtk_expander_set_expanded:
1380 * @expander: a #GtkExpander
1381 * @expanded: whether the child widget is revealed
1383 * Sets the state of the expander. Set to %TRUE, if you want
1384 * the child widget to be revealed, and %FALSE if you want the
1385 * child widget to be hidden.
1390 gtk_expander_set_expanded (GtkExpander *expander,
1393 GtkExpanderPrivate *priv;
1395 g_return_if_fail (GTK_IS_EXPANDER (expander));
1397 priv = expander->priv;
1399 expanded = expanded != FALSE;
1401 if (priv->expanded != expanded)
1403 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (expander));
1404 gboolean enable_animations;
1406 priv->expanded = expanded;
1408 g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
1410 if (enable_animations && gtk_widget_get_realized (GTK_WIDGET (expander)))
1412 gtk_expander_start_animation (expander);
1416 priv->expander_style = expanded ? GTK_EXPANDER_EXPANDED :
1417 GTK_EXPANDER_COLLAPSED;
1419 if (GTK_BIN (expander)->child)
1421 gtk_widget_set_child_visible (GTK_BIN (expander)->child, priv->expanded);
1422 gtk_widget_queue_resize (GTK_WIDGET (expander));
1426 g_object_notify (G_OBJECT (expander), "expanded");
1431 * gtk_expander_get_expanded:
1432 * @expander:a #GtkExpander
1434 * Queries a #GtkExpander and returns its current state. Returns %TRUE
1435 * if the child widget is revealed.
1437 * See gtk_expander_set_expanded().
1439 * Return value: the current state of the expander.
1444 gtk_expander_get_expanded (GtkExpander *expander)
1446 g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1448 return expander->priv->expanded;
1452 * gtk_expander_set_spacing:
1453 * @expander: a #GtkExpander
1454 * @spacing: distance between the expander and child in pixels.
1456 * Sets the spacing field of @expander, which is the number of pixels to
1457 * place between expander and the child.
1462 gtk_expander_set_spacing (GtkExpander *expander,
1465 g_return_if_fail (GTK_IS_EXPANDER (expander));
1466 g_return_if_fail (spacing >= 0);
1468 if (expander->priv->spacing != spacing)
1470 expander->priv->spacing = spacing;
1472 gtk_widget_queue_resize (GTK_WIDGET (expander));
1474 g_object_notify (G_OBJECT (expander), "spacing");
1479 * gtk_expander_get_spacing:
1480 * @expander: a #GtkExpander
1482 * Gets the value set by gtk_expander_set_spacing().
1484 * Return value: spacing between the expander and child.
1489 gtk_expander_get_spacing (GtkExpander *expander)
1491 g_return_val_if_fail (GTK_IS_EXPANDER (expander), 0);
1493 return expander->priv->spacing;
1497 * gtk_expander_set_label:
1498 * @expander: a #GtkExpander
1499 * @label: (allow-none): a string
1501 * Sets the text of the label of the expander to @label.
1503 * This will also clear any previously set labels.
1508 gtk_expander_set_label (GtkExpander *expander,
1511 g_return_if_fail (GTK_IS_EXPANDER (expander));
1515 gtk_expander_set_label_widget (expander, NULL);
1521 child = gtk_label_new (label);
1522 gtk_label_set_use_underline (GTK_LABEL (child), expander->priv->use_underline);
1523 gtk_label_set_use_markup (GTK_LABEL (child), expander->priv->use_markup);
1524 gtk_widget_show (child);
1526 gtk_expander_set_label_widget (expander, child);
1529 g_object_notify (G_OBJECT (expander), "label");
1533 * gtk_expander_get_label:
1534 * @expander: a #GtkExpander
1536 * Fetches the text from a label widget including any embedded
1537 * underlines indicating mnemonics and Pango markup, as set by
1538 * gtk_expander_set_label(). If the label text has not been set the
1539 * return value will be %NULL. This will be the case if you create an
1540 * empty button with gtk_button_new() to use as a container.
1542 * Note that this function behaved differently in versions prior to
1543 * 2.14 and used to return the label text stripped of embedded
1544 * underlines indicating mnemonics and Pango markup. This problem can
1545 * be avoided by fetching the label text directly from the label
1548 * Return value: The text of the label widget. This string is owned
1549 * by the widget and must not be modified or freed.
1553 G_CONST_RETURN char *
1554 gtk_expander_get_label (GtkExpander *expander)
1556 GtkExpanderPrivate *priv;
1558 g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1560 priv = expander->priv;
1562 if (GTK_IS_LABEL (priv->label_widget))
1563 return gtk_label_get_label (GTK_LABEL (priv->label_widget));
1569 * gtk_expander_set_use_underline:
1570 * @expander: a #GtkExpander
1571 * @use_underline: %TRUE if underlines in the text indicate mnemonics
1573 * If true, an underline in the text of the expander label indicates
1574 * the next character should be used for the mnemonic accelerator key.
1579 gtk_expander_set_use_underline (GtkExpander *expander,
1580 gboolean use_underline)
1582 GtkExpanderPrivate *priv;
1584 g_return_if_fail (GTK_IS_EXPANDER (expander));
1586 priv = expander->priv;
1588 use_underline = use_underline != FALSE;
1590 if (priv->use_underline != use_underline)
1592 priv->use_underline = use_underline;
1594 if (GTK_IS_LABEL (priv->label_widget))
1595 gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), use_underline);
1597 g_object_notify (G_OBJECT (expander), "use-underline");
1602 * gtk_expander_get_use_underline:
1603 * @expander: a #GtkExpander
1605 * Returns whether an embedded underline in the expander label indicates a
1606 * mnemonic. See gtk_expander_set_use_underline().
1608 * Return value: %TRUE if an embedded underline in the expander label
1609 * indicates the mnemonic accelerator keys.
1614 gtk_expander_get_use_underline (GtkExpander *expander)
1616 g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1618 return expander->priv->use_underline;
1622 * gtk_expander_set_use_markup:
1623 * @expander: a #GtkExpander
1624 * @use_markup: %TRUE if the label's text should be parsed for markup
1626 * Sets whether the text of the label contains markup in <link
1627 * linkend="PangoMarkupFormat">Pango's text markup
1628 * language</link>. See gtk_label_set_markup().
1633 gtk_expander_set_use_markup (GtkExpander *expander,
1634 gboolean use_markup)
1636 GtkExpanderPrivate *priv;
1638 g_return_if_fail (GTK_IS_EXPANDER (expander));
1640 priv = expander->priv;
1642 use_markup = use_markup != FALSE;
1644 if (priv->use_markup != use_markup)
1646 priv->use_markup = use_markup;
1648 if (GTK_IS_LABEL (priv->label_widget))
1649 gtk_label_set_use_markup (GTK_LABEL (priv->label_widget), use_markup);
1651 g_object_notify (G_OBJECT (expander), "use-markup");
1656 * gtk_expander_get_use_markup:
1657 * @expander: a #GtkExpander
1659 * Returns whether the label's text is interpreted as marked up with
1660 * the <link linkend="PangoMarkupFormat">Pango text markup
1661 * language</link>. See gtk_expander_set_use_markup ().
1663 * Return value: %TRUE if the label's text will be parsed for markup
1668 gtk_expander_get_use_markup (GtkExpander *expander)
1670 g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1672 return expander->priv->use_markup;
1676 * gtk_expander_set_label_widget:
1677 * @expander: a #GtkExpander
1678 * @label_widget: (allow-none): the new label widget
1680 * Set the label widget for the expander. This is the widget
1681 * that will appear embedded alongside the expander arrow.
1686 gtk_expander_set_label_widget (GtkExpander *expander,
1687 GtkWidget *label_widget)
1689 GtkExpanderPrivate *priv;
1692 g_return_if_fail (GTK_IS_EXPANDER (expander));
1693 g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
1694 g_return_if_fail (label_widget == NULL || label_widget->parent == NULL);
1696 priv = expander->priv;
1698 if (priv->label_widget == label_widget)
1701 if (priv->label_widget)
1703 gtk_widget_set_state (priv->label_widget, GTK_STATE_NORMAL);
1704 gtk_widget_unparent (priv->label_widget);
1707 priv->label_widget = label_widget;
1708 widget = GTK_WIDGET (expander);
1712 priv->label_widget = label_widget;
1714 gtk_widget_set_parent (label_widget, widget);
1717 gtk_widget_set_state (label_widget, GTK_STATE_PRELIGHT);
1720 if (gtk_widget_get_visible (widget))
1721 gtk_widget_queue_resize (widget);
1723 g_object_freeze_notify (G_OBJECT (expander));
1724 g_object_notify (G_OBJECT (expander), "label-widget");
1725 g_object_notify (G_OBJECT (expander), "label");
1726 g_object_thaw_notify (G_OBJECT (expander));
1730 * gtk_expander_get_label_widget:
1731 * @expander: a #GtkExpander
1733 * Retrieves the label widget for the frame. See
1734 * gtk_expander_set_label_widget().
1736 * Return value: the label widget, or %NULL if there is none.
1741 gtk_expander_get_label_widget (GtkExpander *expander)
1743 g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1745 return expander->priv->label_widget;