]> Pileus Git - ~andy/gtk/blob - gtk/gtkexpander.c
270769640c525d590a1a665c3cb1946d222af43a
[~andy/gtk] / gtk / gtkexpander.c
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, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Authors:
21  *      Mark McLoughlin <mark@skynet.ie>
22  */
23
24 #include "config.h"
25 #include <string.h>
26 #include "gtkexpander.h"
27
28 #include "gtklabel.h"
29 #include "gtkbuildable.h"
30 #include "gtkcontainer.h"
31 #include "gtkmarshalers.h"
32 #include "gtkmain.h"
33 #include "gtkintl.h"
34 #include "gtkprivate.h"
35 #include <gdk/gdkkeysyms.h>
36 #include "gtkdnd.h"
37
38 #define GTK_EXPANDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_EXPANDER, GtkExpanderPrivate))
39
40 #define DEFAULT_EXPANDER_SIZE 10
41 #define DEFAULT_EXPANDER_SPACING 2
42
43 enum
44 {
45   PROP_0,
46   PROP_EXPANDED,
47   PROP_LABEL,
48   PROP_USE_UNDERLINE,
49   PROP_USE_MARKUP,
50   PROP_SPACING,
51   PROP_LABEL_WIDGET
52 };
53
54 struct _GtkExpanderPrivate
55 {
56   GtkWidget        *label_widget;
57   GdkWindow        *event_window;
58   gint              spacing;
59
60   GtkExpanderStyle  expander_style;
61   guint             animation_timeout;
62   guint             expand_timer;
63
64   guint             expanded : 1;
65   guint             use_underline : 1;
66   guint             use_markup : 1; 
67   guint             button_down : 1;
68   guint             prelight : 1;
69 };
70
71 static void gtk_expander_set_property (GObject          *object,
72                                        guint             prop_id,
73                                        const GValue     *value,
74                                        GParamSpec       *pspec);
75 static void gtk_expander_get_property (GObject          *object,
76                                        guint             prop_id,
77                                        GValue           *value,
78                                        GParamSpec       *pspec);
79
80 static void gtk_expander_destroy (GtkObject *object);
81
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,
108                                              gint              x,
109                                              gint              y,
110                                              guint             time);
111 static void     gtk_expander_drag_leave     (GtkWidget        *widget,
112                                              GdkDragContext   *context,
113                                              guint             time);
114
115 static void gtk_expander_add    (GtkContainer *container,
116                                  GtkWidget    *widget);
117 static void gtk_expander_remove (GtkContainer *container,
118                                  GtkWidget    *widget);
119 static void gtk_expander_forall (GtkContainer *container,
120                                  gboolean        include_internals,
121                                  GtkCallback     callback,
122                                  gpointer        callback_data);
123
124 static void gtk_expander_activate (GtkExpander *expander);
125
126 static void get_expander_bounds (GtkExpander  *expander,
127                                  GdkRectangle *rect);
128
129 /* GtkBuildable */
130 static void gtk_expander_buildable_init           (GtkBuildableIface *iface);
131 static void gtk_expander_buildable_add_child      (GtkBuildable *buildable,
132                                                    GtkBuilder   *builder,
133                                                    GObject      *child,
134                                                    const gchar  *type);
135
136 G_DEFINE_TYPE_WITH_CODE (GtkExpander, gtk_expander, GTK_TYPE_BIN,
137                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
138                                                 gtk_expander_buildable_init))
139
140 static void
141 gtk_expander_class_init (GtkExpanderClass *klass)
142 {
143   GObjectClass *gobject_class;
144   GtkObjectClass *object_class;
145   GtkWidgetClass *widget_class;
146   GtkContainerClass *container_class;
147
148   gobject_class   = (GObjectClass *) klass;
149   object_class    = (GtkObjectClass *) klass;
150   widget_class    = (GtkWidgetClass *) klass;
151   container_class = (GtkContainerClass *) klass;
152
153   gobject_class->set_property = gtk_expander_set_property;
154   gobject_class->get_property = gtk_expander_get_property;
155
156   object_class->destroy = gtk_expander_destroy;
157
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;
174
175   container_class->add    = gtk_expander_add;
176   container_class->remove = gtk_expander_remove;
177   container_class->forall = gtk_expander_forall;
178
179   klass->activate = gtk_expander_activate;
180
181   g_type_class_add_private (klass, sizeof (GtkExpanderPrivate));
182
183   g_object_class_install_property (gobject_class,
184                                    PROP_EXPANDED,
185                                    g_param_spec_boolean ("expanded",
186                                                          P_("Expanded"),
187                                                          P_("Whether the expander has been opened to reveal the child widget"),
188                                                          FALSE,
189                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
190
191   g_object_class_install_property (gobject_class,
192                                    PROP_LABEL,
193                                    g_param_spec_string ("label",
194                                                         P_("Label"),
195                                                         P_("Text of the expander's label"),
196                                                         NULL,
197                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
198
199   g_object_class_install_property (gobject_class,
200                                    PROP_USE_UNDERLINE,
201                                    g_param_spec_boolean ("use-underline",
202                                                          P_("Use underline"),
203                                                          P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
204                                                          FALSE,
205                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
206
207   g_object_class_install_property (gobject_class,
208                                    PROP_USE_MARKUP,
209                                    g_param_spec_boolean ("use-markup",
210                                                          P_("Use markup"),
211                                                          P_("The text of the label includes XML markup. See pango_parse_markup()"),
212                                                          FALSE,
213                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
214
215   g_object_class_install_property (gobject_class,
216                                    PROP_SPACING,
217                                    g_param_spec_int ("spacing",
218                                                      P_("Spacing"),
219                                                      P_("Space to put between the label and the child"),
220                                                      0,
221                                                      G_MAXINT,
222                                                      0,
223                                                      GTK_PARAM_READWRITE));
224
225   g_object_class_install_property (gobject_class,
226                                    PROP_LABEL_WIDGET,
227                                    g_param_spec_object ("label-widget",
228                                                         P_("Label widget"),
229                                                         P_("A widget to display in place of the usual expander label"),
230                                                         GTK_TYPE_WIDGET,
231                                                         GTK_PARAM_READWRITE));
232
233   gtk_widget_class_install_style_property (widget_class,
234                                            g_param_spec_int ("expander-size",
235                                                              P_("Expander Size"),
236                                                              P_("Size of the expander arrow"),
237                                                              0,
238                                                              G_MAXINT,
239                                                              DEFAULT_EXPANDER_SIZE,
240                                                              GTK_PARAM_READABLE));
241
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"),
246                                                              0,
247                                                              G_MAXINT,
248                                                              DEFAULT_EXPANDER_SPACING,
249                                                              GTK_PARAM_READABLE));
250
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),
256                   NULL, NULL,
257                   _gtk_marshal_VOID__VOID,
258                   G_TYPE_NONE, 0);
259 }
260
261 static void
262 gtk_expander_init (GtkExpander *expander)
263 {
264   GtkExpanderPrivate *priv;
265
266   expander->priv = priv = GTK_EXPANDER_GET_PRIVATE (expander);
267
268   gtk_widget_set_can_focus (GTK_WIDGET (expander), TRUE);
269   gtk_widget_set_has_window (GTK_WIDGET (expander), FALSE);
270
271   priv->label_widget = NULL;
272   priv->event_window = NULL;
273   priv->spacing = 0;
274
275   priv->expander_style = GTK_EXPANDER_COLLAPSED;
276   priv->animation_timeout = 0;
277
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;
284
285   gtk_drag_dest_set (GTK_WIDGET (expander), 0, NULL, 0, 0);
286   gtk_drag_dest_set_track_motion (GTK_WIDGET (expander), TRUE);
287 }
288
289 static void
290 gtk_expander_buildable_add_child (GtkBuildable  *buildable,
291                                   GtkBuilder    *builder,
292                                   GObject       *child,
293                                   const gchar   *type)
294 {
295   if (!type)
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));
299   else
300     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GTK_EXPANDER (buildable), type);
301 }
302
303 static void
304 gtk_expander_buildable_init (GtkBuildableIface *iface)
305 {
306   iface->add_child = gtk_expander_buildable_add_child;
307 }
308
309 static void
310 gtk_expander_set_property (GObject      *object,
311                            guint         prop_id,
312                            const GValue *value,
313                            GParamSpec   *pspec)
314 {
315   GtkExpander *expander = GTK_EXPANDER (object);
316                                                                                                              
317   switch (prop_id)
318     {
319     case PROP_EXPANDED:
320       gtk_expander_set_expanded (expander, g_value_get_boolean (value));
321       break;
322     case PROP_LABEL:
323       gtk_expander_set_label (expander, g_value_get_string (value));
324       break;
325     case PROP_USE_UNDERLINE:
326       gtk_expander_set_use_underline (expander, g_value_get_boolean (value));
327       break;
328     case PROP_USE_MARKUP:
329       gtk_expander_set_use_markup (expander, g_value_get_boolean (value));
330       break;
331     case PROP_SPACING:
332       gtk_expander_set_spacing (expander, g_value_get_int (value));
333       break;
334     case PROP_LABEL_WIDGET:
335       gtk_expander_set_label_widget (expander, g_value_get_object (value));
336       break;
337     default:
338       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
339       break;
340     }
341 }
342
343 static void
344 gtk_expander_get_property (GObject    *object,
345                            guint       prop_id,
346                            GValue     *value,
347                            GParamSpec *pspec)
348 {
349   GtkExpander *expander = GTK_EXPANDER (object);
350   GtkExpanderPrivate *priv = expander->priv;
351
352   switch (prop_id)
353     {
354     case PROP_EXPANDED:
355       g_value_set_boolean (value, priv->expanded);
356       break;
357     case PROP_LABEL:
358       g_value_set_string (value, gtk_expander_get_label (expander));
359       break;
360     case PROP_USE_UNDERLINE:
361       g_value_set_boolean (value, priv->use_underline);
362       break;
363     case PROP_USE_MARKUP:
364       g_value_set_boolean (value, priv->use_markup);
365       break;
366     case PROP_SPACING:
367       g_value_set_int (value, priv->spacing);
368       break;
369     case PROP_LABEL_WIDGET:
370       g_value_set_object (value,
371                           priv->label_widget ?
372                           G_OBJECT (priv->label_widget) : NULL);
373       break;
374     default:
375       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
376       break;
377     }
378 }
379
380 static void
381 gtk_expander_destroy (GtkObject *object)
382 {
383   GtkExpanderPrivate *priv = GTK_EXPANDER (object)->priv;
384   
385   if (priv->animation_timeout)
386     {
387       g_source_remove (priv->animation_timeout);
388       priv->animation_timeout = 0;
389     }
390   
391   GTK_OBJECT_CLASS (gtk_expander_parent_class)->destroy (object);
392 }
393
394 static void
395 gtk_expander_realize (GtkWidget *widget)
396 {
397   GtkExpanderPrivate *priv;
398   GdkWindowAttr attributes;
399   gint attributes_mask;
400   gint border_width;
401   GdkRectangle expander_rect;
402   gint label_height;
403
404   priv = GTK_EXPANDER (widget)->priv;
405   gtk_widget_set_realized (widget, TRUE);
406
407   border_width = GTK_CONTAINER (widget)->border_width;
408
409   get_expander_bounds (GTK_EXPANDER (widget), &expander_rect);
410   
411   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
412     {
413       GtkRequisition label_requisition;
414
415       gtk_widget_get_child_requisition (priv->label_widget, &label_requisition);
416       label_height = label_requisition.height;
417     }
418   else
419     label_height = 0;
420
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;
432
433   attributes_mask = GDK_WA_X | GDK_WA_Y;
434
435   widget->window = gtk_widget_get_parent_window (widget);
436   g_object_ref (widget->window);
437
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);
441
442   widget->style = gtk_style_attach (widget->style, widget->window);
443 }
444
445 static void
446 gtk_expander_unrealize (GtkWidget *widget)
447 {
448   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
449
450   if (priv->event_window)
451     {
452       gdk_window_set_user_data (priv->event_window, NULL);
453       gdk_window_destroy (priv->event_window);
454       priv->event_window = NULL;
455     }
456
457   GTK_WIDGET_CLASS (gtk_expander_parent_class)->unrealize (widget);
458 }
459
460 static void
461 gtk_expander_size_request (GtkWidget      *widget,
462                            GtkRequisition *requisition)
463 {
464   GtkExpander *expander;
465   GtkBin *bin;
466   GtkExpanderPrivate *priv;
467   gint border_width;
468   gint expander_size;
469   gint expander_spacing;
470   gboolean interior_focus;
471   gint focus_width;
472   gint focus_pad;
473
474   bin = GTK_BIN (widget);
475   expander = GTK_EXPANDER (widget);
476   priv = expander->priv;
477
478   border_width = GTK_CONTAINER (widget)->border_width;
479
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,
486                         NULL);
487
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;
491
492   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
493     {
494       GtkRequisition label_requisition;
495
496       gtk_widget_size_request (priv->label_widget, &label_requisition);
497
498       requisition->width  += label_requisition.width;
499       requisition->height += label_requisition.height;
500     }
501
502   requisition->height = MAX (expander_size + 2 * expander_spacing, requisition->height);
503
504   if (!interior_focus)
505     requisition->height += 2 * focus_width + 2 * focus_pad;
506
507   if (bin->child && GTK_WIDGET_CHILD_VISIBLE (bin->child))
508     {
509       GtkRequisition child_requisition;
510
511       gtk_widget_size_request (bin->child, &child_requisition);
512
513       requisition->width = MAX (requisition->width, child_requisition.width);
514       requisition->height += child_requisition.height + priv->spacing;
515     }
516
517   requisition->width  += 2 * border_width;
518   requisition->height += 2 * border_width;
519 }
520
521 static void
522 get_expander_bounds (GtkExpander  *expander,
523                      GdkRectangle *rect)
524 {
525   GtkWidget *widget;
526   GtkExpanderPrivate *priv;
527   gint border_width;
528   gint expander_size;
529   gint expander_spacing;
530   gboolean interior_focus;
531   gint focus_width;
532   gint focus_pad;
533   gboolean ltr;
534
535   widget = GTK_WIDGET (expander);
536   priv = expander->priv;
537
538   border_width = GTK_CONTAINER (expander)->border_width;
539
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,
546                         NULL);
547
548   ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
549
550   rect->x = widget->allocation.x + border_width;
551   rect->y = widget->allocation.y + border_width;
552
553   if (ltr)
554     rect->x += expander_spacing;
555   else
556     rect->x += widget->allocation.width - 2 * border_width -
557                expander_spacing - expander_size;
558
559   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
560     {
561       GtkAllocation label_allocation;
562
563       label_allocation = priv->label_widget->allocation;
564
565       if (expander_size < label_allocation.height)
566         rect->y += focus_width + focus_pad + (label_allocation.height - expander_size) / 2;
567       else
568         rect->y += expander_spacing;
569     }
570   else
571     {
572       rect->y += expander_spacing;
573     }
574
575   if (!interior_focus)
576     {
577       if (ltr)
578         rect->x += focus_width + focus_pad;
579       else
580         rect->x -= focus_width + focus_pad;
581       rect->y += focus_width + focus_pad;
582     }
583
584   rect->width = rect->height = expander_size;
585 }
586
587 static void
588 gtk_expander_size_allocate (GtkWidget     *widget,
589                             GtkAllocation *allocation)
590 {
591   GtkExpander *expander;
592   GtkBin *bin;
593   GtkExpanderPrivate *priv;
594   GtkRequisition child_requisition;
595   gboolean child_visible = FALSE;
596   gint border_width;
597   gint expander_size;
598   gint expander_spacing;
599   gboolean interior_focus;
600   gint focus_width;
601   gint focus_pad;
602   gint label_height;
603
604   expander = GTK_EXPANDER (widget);
605   bin = GTK_BIN (widget);
606   priv = expander->priv;
607
608   border_width = GTK_CONTAINER (widget)->border_width;
609
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,
616                         NULL);
617
618   child_requisition.width = 0;
619   child_requisition.height = 0;
620   if (bin->child && GTK_WIDGET_CHILD_VISIBLE (bin->child))
621     {
622       child_visible = TRUE;
623       gtk_widget_get_child_requisition (bin->child, &child_requisition);
624     }
625
626   widget->allocation = *allocation;
627
628   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
629     {
630       GtkAllocation label_allocation;
631       GtkRequisition label_requisition;
632       gboolean ltr;
633
634       gtk_widget_get_child_requisition (priv->label_widget, &label_requisition);
635
636       ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
637
638       if (ltr)
639         label_allocation.x = (widget->allocation.x +
640                               border_width + focus_width + focus_pad +
641                               expander_size + 2 * expander_spacing);
642       else
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));
647
648       label_allocation.y = widget->allocation.y + border_width + focus_width + focus_pad;
649
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);
655
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);
661
662       gtk_widget_size_allocate (priv->label_widget, &label_allocation);
663
664       label_height = label_allocation.height;
665     }
666   else
667     {
668       label_height = 0;
669     }
670
671   if (gtk_widget_get_realized (widget))
672     {
673       GdkRectangle rect;
674
675       get_expander_bounds (expander, &rect);
676
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));
682     }
683
684   if (child_visible)
685     {
686       GtkAllocation child_allocation;
687       gint top_height;
688
689       top_height = MAX (2 * expander_spacing + expander_size,
690                         label_height +
691                         (interior_focus ? 2 * focus_width + 2 * focus_pad : 0));
692
693       child_allocation.x = widget->allocation.x + border_width;
694       child_allocation.y = widget->allocation.y + border_width + top_height + priv->spacing;
695
696       if (!interior_focus)
697         child_allocation.y += 2 * focus_width + 2 * focus_pad;
698
699       child_allocation.width = MAX (allocation->width - 2 * border_width, 1);
700
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);
705
706       gtk_widget_size_allocate (bin->child, &child_allocation);
707     }
708 }
709
710 static void
711 gtk_expander_map (GtkWidget *widget)
712 {
713   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
714
715   if (priv->label_widget)
716     gtk_widget_map (priv->label_widget);
717
718   GTK_WIDGET_CLASS (gtk_expander_parent_class)->map (widget);
719
720   if (priv->event_window)
721     gdk_window_show (priv->event_window);
722 }
723
724 static void
725 gtk_expander_unmap (GtkWidget *widget)
726 {
727   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
728
729   if (priv->event_window)
730     gdk_window_hide (priv->event_window);
731
732   GTK_WIDGET_CLASS (gtk_expander_parent_class)->unmap (widget);
733
734   if (priv->label_widget)
735     gtk_widget_unmap (priv->label_widget);
736 }
737
738 static void
739 gtk_expander_paint_prelight (GtkExpander *expander)
740 {
741   GtkWidget *widget;
742   GtkContainer *container;
743   GtkExpanderPrivate *priv;
744   GdkRectangle area;
745   gboolean interior_focus;
746   int focus_width;
747   int focus_pad;
748   int expander_size;
749   int expander_spacing;
750
751   priv = expander->priv;
752   widget = GTK_WIDGET (expander);
753   container = GTK_CONTAINER (expander);
754
755   gtk_widget_style_get (widget,
756                         "interior-focus", &interior_focus,
757                         "focus-line-width", &focus_width,
758                         "focus-padding", &focus_pad,
759                         "expander-size", &expander_size,
760                         "expander-spacing", &expander_spacing,
761                         NULL);
762
763   area.x = widget->allocation.x + container->border_width;
764   area.y = widget->allocation.y + container->border_width;
765   area.width = widget->allocation.width - (2 * container->border_width);
766
767   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
768     area.height = priv->label_widget->allocation.height;
769   else
770     area.height = 0;
771
772   area.height += interior_focus ? (focus_width + focus_pad) * 2 : 0;
773   area.height = MAX (area.height, expander_size + 2 * expander_spacing);
774   area.height += !interior_focus ? (focus_width + focus_pad) * 2 : 0;
775
776   gtk_paint_flat_box (widget->style, widget->window,
777                       GTK_STATE_PRELIGHT,
778                       GTK_SHADOW_ETCHED_OUT,
779                       &area, widget, "expander",
780                       area.x, area.y,
781                       area.width, area.height);
782 }
783
784 static void
785 gtk_expander_paint (GtkExpander *expander)
786 {
787   GtkWidget *widget;
788   GdkRectangle clip;
789   GtkStateType state;
790
791   widget = GTK_WIDGET (expander);
792
793   get_expander_bounds (expander, &clip);
794
795   state = widget->state;
796   if (expander->priv->prelight)
797     {
798       state = GTK_STATE_PRELIGHT;
799
800       gtk_expander_paint_prelight (expander);
801     }
802
803   gtk_paint_expander (widget->style,
804                       widget->window,
805                       state,
806                       &clip,
807                       widget,
808                       "expander",
809                       clip.x + clip.width / 2,
810                       clip.y + clip.height / 2,
811                       expander->priv->expander_style);
812 }
813
814 static void
815 gtk_expander_paint_focus (GtkExpander  *expander,
816                           GdkRectangle *area)
817 {
818   GtkWidget *widget;
819   GtkExpanderPrivate *priv;
820   GdkRectangle rect;
821   gint x, y, width, height;
822   gboolean interior_focus;
823   gint border_width;
824   gint focus_width;
825   gint focus_pad;
826   gint expander_size;
827   gint expander_spacing;
828   gboolean ltr;
829
830   widget = GTK_WIDGET (expander);
831   priv = expander->priv;
832
833   border_width = GTK_CONTAINER (widget)->border_width;
834
835   gtk_widget_style_get (widget,
836                         "interior-focus", &interior_focus,
837                         "focus-line-width", &focus_width,
838                         "focus-padding", &focus_pad,
839                         "expander-size", &expander_size,
840                         "expander-spacing", &expander_spacing,
841                         NULL);
842
843   ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
844   
845   width = height = 0;
846
847   if (priv->label_widget)
848     {
849       if (gtk_widget_get_visible (priv->label_widget))
850         {
851           GtkAllocation label_allocation = priv->label_widget->allocation;
852
853           width  = label_allocation.width;
854           height = label_allocation.height;
855         }
856
857       width  += 2 * focus_pad + 2 * focus_width;
858       height += 2 * focus_pad + 2 * focus_width;
859
860       x = widget->allocation.x + border_width;
861       y = widget->allocation.y + border_width;
862
863       if (ltr)
864         {
865           if (interior_focus)
866             x += expander_spacing * 2 + expander_size;
867         }
868       else
869         {
870           x += widget->allocation.width - 2 * border_width
871             - expander_spacing * 2 - expander_size - width;
872         }
873
874       if (!interior_focus)
875         {
876           width += expander_size + 2 * expander_spacing;
877           height = MAX (height, expander_size + 2 * expander_spacing);
878         }
879     }
880   else
881     {
882       get_expander_bounds (expander, &rect);
883
884       x = rect.x - focus_pad;
885       y = rect.y - focus_pad;
886       width = rect.width + 2 * focus_pad;
887       height = rect.height + 2 * focus_pad;
888     }
889       
890   gtk_paint_focus (widget->style, widget->window, gtk_widget_get_state (widget),
891                    area, widget, "expander",
892                    x, y, width, height);
893 }
894
895 static gboolean
896 gtk_expander_expose (GtkWidget      *widget,
897                      GdkEventExpose *event)
898 {
899   if (gtk_widget_is_drawable (widget))
900     {
901       GtkExpander *expander = GTK_EXPANDER (widget);
902
903       gtk_expander_paint (expander);
904
905       if (gtk_widget_has_focus (widget))
906         gtk_expander_paint_focus (expander, &event->area);
907
908       GTK_WIDGET_CLASS (gtk_expander_parent_class)->expose_event (widget, event);
909     }
910
911   return FALSE;
912 }
913
914 static gboolean
915 gtk_expander_button_press (GtkWidget      *widget,
916                            GdkEventButton *event)
917 {
918   GtkExpander *expander = GTK_EXPANDER (widget);
919
920   if (event->button == 1 && event->window == expander->priv->event_window)
921     {
922       expander->priv->button_down = TRUE;
923       return TRUE;
924     }
925
926   return FALSE;
927 }
928
929 static gboolean
930 gtk_expander_button_release (GtkWidget      *widget,
931                              GdkEventButton *event)
932 {
933   GtkExpander *expander = GTK_EXPANDER (widget);
934
935   if (event->button == 1 && expander->priv->button_down)
936     {
937       gtk_widget_activate (widget);
938       expander->priv->button_down = FALSE;
939       return TRUE;
940     }
941
942   return FALSE;
943 }
944
945 static void
946 gtk_expander_grab_notify (GtkWidget *widget,
947                           gboolean   was_grabbed)
948 {
949   if (!was_grabbed)
950     GTK_EXPANDER (widget)->priv->button_down = FALSE;
951 }
952
953 static void
954 gtk_expander_state_changed (GtkWidget    *widget,
955                             GtkStateType  previous_state)
956 {
957   if (!gtk_widget_is_sensitive (widget))
958     GTK_EXPANDER (widget)->priv->button_down = FALSE;
959 }
960
961 static void
962 gtk_expander_redraw_expander (GtkExpander *expander)
963 {
964   GtkWidget *widget;
965
966   widget = GTK_WIDGET (expander);
967
968   if (gtk_widget_get_realized (widget))
969     gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE);
970 }
971
972 static gboolean
973 gtk_expander_enter_notify (GtkWidget        *widget,
974                            GdkEventCrossing *event)
975 {
976   GtkExpander *expander = GTK_EXPANDER (widget);
977   GtkWidget *event_widget;
978
979   event_widget = gtk_get_event_widget ((GdkEvent *) event);
980
981   if (event_widget == widget &&
982       event->detail != GDK_NOTIFY_INFERIOR)
983     {
984       expander->priv->prelight = TRUE;
985
986       if (expander->priv->label_widget)
987         gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_PRELIGHT);
988
989       gtk_expander_redraw_expander (expander);
990     }
991
992   return FALSE;
993 }
994
995 static gboolean
996 gtk_expander_leave_notify (GtkWidget        *widget,
997                            GdkEventCrossing *event)
998 {
999   GtkExpander *expander = GTK_EXPANDER (widget);
1000   GtkWidget *event_widget;
1001
1002   event_widget = gtk_get_event_widget ((GdkEvent *) event);
1003
1004   if (event_widget == widget &&
1005       event->detail != GDK_NOTIFY_INFERIOR)
1006     {
1007       expander->priv->prelight = FALSE;
1008
1009       if (expander->priv->label_widget)
1010         gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_NORMAL);
1011
1012       gtk_expander_redraw_expander (expander);
1013     }
1014
1015   return FALSE;
1016 }
1017
1018 static gboolean
1019 expand_timeout (gpointer data)
1020 {
1021   GtkExpander *expander = GTK_EXPANDER (data);
1022   GtkExpanderPrivate *priv = expander->priv;
1023
1024   priv->expand_timer = 0;
1025   gtk_expander_set_expanded (expander, TRUE);
1026
1027   return FALSE;
1028 }
1029
1030 static gboolean
1031 gtk_expander_drag_motion (GtkWidget        *widget,
1032                           GdkDragContext   *context,
1033                           gint              x,
1034                           gint              y,
1035                           guint             time)
1036 {
1037   GtkExpander *expander = GTK_EXPANDER (widget);
1038   GtkExpanderPrivate *priv = expander->priv;
1039
1040   if (!priv->expanded && !priv->expand_timer)
1041     {
1042       GtkSettings *settings;
1043       guint timeout;
1044
1045       settings = gtk_widget_get_settings (widget);
1046       g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
1047
1048       priv->expand_timer = gdk_threads_add_timeout (timeout, (GSourceFunc) expand_timeout, expander);
1049     }
1050
1051   return TRUE;
1052 }
1053
1054 static void
1055 gtk_expander_drag_leave (GtkWidget      *widget,
1056                          GdkDragContext *context,
1057                          guint           time)
1058 {
1059   GtkExpander *expander = GTK_EXPANDER (widget);
1060   GtkExpanderPrivate *priv = expander->priv;
1061
1062   if (priv->expand_timer)
1063     {
1064       g_source_remove (priv->expand_timer);
1065       priv->expand_timer = 0;
1066     }
1067 }
1068
1069 typedef enum
1070 {
1071   FOCUS_NONE,
1072   FOCUS_WIDGET,
1073   FOCUS_LABEL,
1074   FOCUS_CHILD
1075 } FocusSite;
1076
1077 static gboolean
1078 focus_current_site (GtkExpander      *expander,
1079                     GtkDirectionType  direction)
1080 {
1081   GtkWidget *current_focus;
1082
1083   current_focus = GTK_CONTAINER (expander)->focus_child;
1084
1085   if (!current_focus)
1086     return FALSE;
1087
1088   return gtk_widget_child_focus (current_focus, direction);
1089 }
1090
1091 static gboolean
1092 focus_in_site (GtkExpander      *expander,
1093                FocusSite         site,
1094                GtkDirectionType  direction)
1095 {
1096   switch (site)
1097     {
1098     case FOCUS_WIDGET:
1099       gtk_widget_grab_focus (GTK_WIDGET (expander));
1100       return TRUE;
1101     case FOCUS_LABEL:
1102       if (expander->priv->label_widget)
1103         return gtk_widget_child_focus (expander->priv->label_widget, direction);
1104       else
1105         return FALSE;
1106     case FOCUS_CHILD:
1107       {
1108         GtkWidget *child = gtk_bin_get_child (GTK_BIN (expander));
1109
1110         if (child && GTK_WIDGET_CHILD_VISIBLE (child))
1111           return gtk_widget_child_focus (child, direction);
1112         else
1113           return FALSE;
1114       }
1115     case FOCUS_NONE:
1116       break;
1117     }
1118
1119   g_assert_not_reached ();
1120   return FALSE;
1121 }
1122
1123 static FocusSite
1124 get_next_site (GtkExpander      *expander,
1125                FocusSite         site,
1126                GtkDirectionType  direction)
1127 {
1128   gboolean ltr;
1129
1130   ltr = gtk_widget_get_direction (GTK_WIDGET (expander)) != GTK_TEXT_DIR_RTL;
1131
1132   switch (site)
1133     {
1134     case FOCUS_NONE:
1135       switch (direction)
1136         {
1137         case GTK_DIR_TAB_BACKWARD:
1138         case GTK_DIR_LEFT:
1139         case GTK_DIR_UP:
1140           return FOCUS_CHILD;
1141         case GTK_DIR_TAB_FORWARD:
1142         case GTK_DIR_DOWN:
1143         case GTK_DIR_RIGHT:
1144           return FOCUS_WIDGET;
1145         }
1146     case FOCUS_WIDGET:
1147       switch (direction)
1148         {
1149         case GTK_DIR_TAB_BACKWARD:
1150         case GTK_DIR_UP:
1151           return FOCUS_NONE;
1152         case GTK_DIR_LEFT:
1153           return ltr ? FOCUS_NONE : FOCUS_LABEL;
1154         case GTK_DIR_TAB_FORWARD:
1155         case GTK_DIR_DOWN:
1156           return FOCUS_LABEL;
1157         case GTK_DIR_RIGHT:
1158           return ltr ? FOCUS_LABEL : FOCUS_NONE;
1159           break;
1160         }
1161     case FOCUS_LABEL:
1162       switch (direction)
1163         {
1164         case GTK_DIR_TAB_BACKWARD:
1165         case GTK_DIR_UP:
1166           return FOCUS_WIDGET;
1167         case GTK_DIR_LEFT:
1168           return ltr ? FOCUS_WIDGET : FOCUS_CHILD;
1169         case GTK_DIR_TAB_FORWARD:
1170         case GTK_DIR_DOWN:
1171           return FOCUS_CHILD;
1172         case GTK_DIR_RIGHT:
1173           return ltr ? FOCUS_CHILD : FOCUS_WIDGET;
1174           break;
1175         }
1176     case FOCUS_CHILD:
1177       switch (direction)
1178         {
1179         case GTK_DIR_TAB_BACKWARD:
1180         case GTK_DIR_LEFT:
1181         case GTK_DIR_UP:
1182           return FOCUS_LABEL;
1183         case GTK_DIR_TAB_FORWARD:
1184         case GTK_DIR_DOWN:
1185         case GTK_DIR_RIGHT:
1186           return FOCUS_NONE;
1187         }
1188     }
1189
1190   g_assert_not_reached ();
1191   return FOCUS_NONE;
1192 }
1193
1194 static gboolean
1195 gtk_expander_focus (GtkWidget        *widget,
1196                     GtkDirectionType  direction)
1197 {
1198   GtkExpander *expander = GTK_EXPANDER (widget);
1199   
1200   if (!focus_current_site (expander, direction))
1201     {
1202       GtkWidget *old_focus_child;
1203       gboolean widget_is_focus;
1204       FocusSite site = FOCUS_NONE;
1205       
1206       widget_is_focus = gtk_widget_is_focus (widget);
1207       old_focus_child = GTK_CONTAINER (widget)->focus_child;
1208       
1209       if (old_focus_child && old_focus_child == expander->priv->label_widget)
1210         site = FOCUS_LABEL;
1211       else if (old_focus_child)
1212         site = FOCUS_CHILD;
1213       else if (widget_is_focus)
1214         site = FOCUS_WIDGET;
1215
1216       while ((site = get_next_site (expander, site, direction)) != FOCUS_NONE)
1217         {
1218           if (focus_in_site (expander, site, direction))
1219             return TRUE;
1220         }
1221
1222       return FALSE;
1223     }
1224
1225   return TRUE;
1226 }
1227
1228 static void
1229 gtk_expander_add (GtkContainer *container,
1230                   GtkWidget    *widget)
1231 {
1232   GTK_CONTAINER_CLASS (gtk_expander_parent_class)->add (container, widget);
1233
1234   gtk_widget_set_child_visible (widget, GTK_EXPANDER (container)->priv->expanded);
1235   gtk_widget_queue_resize (GTK_WIDGET (container));
1236 }
1237
1238 static void
1239 gtk_expander_remove (GtkContainer *container,
1240                      GtkWidget    *widget)
1241 {
1242   GtkExpander *expander = GTK_EXPANDER (container);
1243
1244   if (GTK_EXPANDER (expander)->priv->label_widget == widget)
1245     gtk_expander_set_label_widget (expander, NULL);
1246   else
1247     GTK_CONTAINER_CLASS (gtk_expander_parent_class)->remove (container, widget);
1248 }
1249
1250 static void
1251 gtk_expander_forall (GtkContainer *container,
1252                      gboolean      include_internals,
1253                      GtkCallback   callback,
1254                      gpointer      callback_data)
1255 {
1256   GtkBin *bin = GTK_BIN (container);
1257   GtkExpanderPrivate *priv = GTK_EXPANDER (container)->priv;
1258
1259   if (bin->child)
1260     (* callback) (bin->child, callback_data);
1261
1262   if (priv->label_widget)
1263     (* callback) (priv->label_widget, callback_data);
1264 }
1265
1266 static void
1267 gtk_expander_activate (GtkExpander *expander)
1268 {
1269   gtk_expander_set_expanded (expander, !expander->priv->expanded);
1270 }
1271
1272 /**
1273  * gtk_expander_new:
1274  * @label: the text of the label
1275  * 
1276  * Creates a new expander using @label as the text of the label.
1277  * 
1278  * Return value: a new #GtkExpander widget.
1279  *
1280  * Since: 2.4
1281  **/
1282 GtkWidget *
1283 gtk_expander_new (const gchar *label)
1284 {
1285   return g_object_new (GTK_TYPE_EXPANDER, "label", label, NULL);
1286 }
1287
1288 /**
1289  * gtk_expander_new_with_mnemonic:
1290  * @label: (allow-none): the text of the label with an underscore in front of the
1291  *         mnemonic character
1292  *
1293  * Creates a new expander using @label as the text of the label.
1294  * If characters in @label are preceded by an underscore, they are underlined.
1295  * If you need a literal underscore character in a label, use '__' (two 
1296  * underscores). The first underlined character represents a keyboard 
1297  * accelerator called a mnemonic.
1298  * Pressing Alt and that key activates the button.
1299  * 
1300  * Return value: a new #GtkExpander widget.
1301  *
1302  * Since: 2.4
1303  **/
1304 GtkWidget *
1305 gtk_expander_new_with_mnemonic (const gchar *label)
1306 {
1307   return g_object_new (GTK_TYPE_EXPANDER,
1308                        "label", label,
1309                        "use-underline", TRUE,
1310                        NULL);
1311 }
1312
1313 static gboolean
1314 gtk_expander_animation_timeout (GtkExpander *expander)
1315 {
1316   GtkExpanderPrivate *priv = expander->priv;
1317   GdkRectangle area;
1318   gboolean finish = FALSE;
1319
1320   if (gtk_widget_get_realized (GTK_WIDGET (expander)))
1321     {
1322       get_expander_bounds (expander, &area);
1323       gdk_window_invalidate_rect (GTK_WIDGET (expander)->window, &area, TRUE);
1324     }
1325
1326   if (priv->expanded)
1327     {
1328       if (priv->expander_style == GTK_EXPANDER_COLLAPSED)
1329         {
1330           priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED;
1331         }
1332       else
1333         {
1334           priv->expander_style = GTK_EXPANDER_EXPANDED;
1335           finish = TRUE;
1336         }
1337     }
1338   else
1339     {
1340       if (priv->expander_style == GTK_EXPANDER_EXPANDED)
1341         {
1342           priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED;
1343         }
1344       else
1345         {
1346           priv->expander_style = GTK_EXPANDER_COLLAPSED;
1347           finish = TRUE;
1348         }
1349     }
1350
1351   if (finish)
1352     {
1353       priv->animation_timeout = 0;
1354       if (GTK_BIN (expander)->child)
1355         gtk_widget_set_child_visible (GTK_BIN (expander)->child, priv->expanded);
1356       gtk_widget_queue_resize (GTK_WIDGET (expander));
1357     }
1358
1359   return !finish;
1360 }
1361
1362 static void
1363 gtk_expander_start_animation (GtkExpander *expander)
1364 {
1365   GtkExpanderPrivate *priv = expander->priv;
1366
1367   if (priv->animation_timeout)
1368     g_source_remove (priv->animation_timeout);
1369
1370   priv->animation_timeout =
1371                 gdk_threads_add_timeout (50,
1372                                (GSourceFunc) gtk_expander_animation_timeout,
1373                                expander);
1374 }
1375
1376 /**
1377  * gtk_expander_set_expanded:
1378  * @expander: a #GtkExpander
1379  * @expanded: whether the child widget is revealed
1380  *
1381  * Sets the state of the expander. Set to %TRUE, if you want
1382  * the child widget to be revealed, and %FALSE if you want the
1383  * child widget to be hidden.
1384  *
1385  * Since: 2.4
1386  **/
1387 void
1388 gtk_expander_set_expanded (GtkExpander *expander,
1389                            gboolean     expanded)
1390 {
1391   GtkExpanderPrivate *priv;
1392
1393   g_return_if_fail (GTK_IS_EXPANDER (expander));
1394
1395   priv = expander->priv;
1396
1397   expanded = expanded != FALSE;
1398
1399   if (priv->expanded != expanded)
1400     {
1401       GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (expander));
1402       gboolean     enable_animations;
1403
1404       priv->expanded = expanded;
1405
1406       g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
1407
1408       if (enable_animations && gtk_widget_get_realized (GTK_WIDGET (expander)))
1409         {
1410           gtk_expander_start_animation (expander);
1411         }
1412       else
1413         {
1414           priv->expander_style = expanded ? GTK_EXPANDER_EXPANDED :
1415                                             GTK_EXPANDER_COLLAPSED;
1416
1417           if (GTK_BIN (expander)->child)
1418             {
1419               gtk_widget_set_child_visible (GTK_BIN (expander)->child, priv->expanded);
1420               gtk_widget_queue_resize (GTK_WIDGET (expander));
1421             }
1422         }
1423
1424       g_object_notify (G_OBJECT (expander), "expanded");
1425     }
1426 }
1427
1428 /**
1429  * gtk_expander_get_expanded:
1430  * @expander:a #GtkExpander
1431  *
1432  * Queries a #GtkExpander and returns its current state. Returns %TRUE
1433  * if the child widget is revealed.
1434  *
1435  * See gtk_expander_set_expanded().
1436  *
1437  * Return value: the current state of the expander.
1438  *
1439  * Since: 2.4
1440  **/
1441 gboolean
1442 gtk_expander_get_expanded (GtkExpander *expander)
1443 {
1444   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1445
1446   return expander->priv->expanded;
1447 }
1448
1449 /**
1450  * gtk_expander_set_spacing:
1451  * @expander: a #GtkExpander
1452  * @spacing: distance between the expander and child in pixels.
1453  *
1454  * Sets the spacing field of @expander, which is the number of pixels to
1455  * place between expander and the child.
1456  *
1457  * Since: 2.4
1458  **/
1459 void
1460 gtk_expander_set_spacing (GtkExpander *expander,
1461                           gint         spacing)
1462 {
1463   g_return_if_fail (GTK_IS_EXPANDER (expander));
1464   g_return_if_fail (spacing >= 0);
1465
1466   if (expander->priv->spacing != spacing)
1467     {
1468       expander->priv->spacing = spacing;
1469
1470       gtk_widget_queue_resize (GTK_WIDGET (expander));
1471
1472       g_object_notify (G_OBJECT (expander), "spacing");
1473     }
1474 }
1475
1476 /**
1477  * gtk_expander_get_spacing:
1478  * @expander: a #GtkExpander
1479  *
1480  * Gets the value set by gtk_expander_set_spacing().
1481  *
1482  * Return value: spacing between the expander and child.
1483  *
1484  * Since: 2.4
1485  **/
1486 gint
1487 gtk_expander_get_spacing (GtkExpander *expander)
1488 {
1489   g_return_val_if_fail (GTK_IS_EXPANDER (expander), 0);
1490
1491   return expander->priv->spacing;
1492 }
1493
1494 /**
1495  * gtk_expander_set_label:
1496  * @expander: a #GtkExpander
1497  * @label: (allow-none): a string
1498  *
1499  * Sets the text of the label of the expander to @label.
1500  *
1501  * This will also clear any previously set labels.
1502  *
1503  * Since: 2.4
1504  **/
1505 void
1506 gtk_expander_set_label (GtkExpander *expander,
1507                         const gchar *label)
1508 {
1509   g_return_if_fail (GTK_IS_EXPANDER (expander));
1510
1511   if (!label)
1512     {
1513       gtk_expander_set_label_widget (expander, NULL);
1514     }
1515   else
1516     {
1517       GtkWidget *child;
1518
1519       child = gtk_label_new (label);
1520       gtk_label_set_use_underline (GTK_LABEL (child), expander->priv->use_underline);
1521       gtk_label_set_use_markup (GTK_LABEL (child), expander->priv->use_markup);
1522       gtk_widget_show (child);
1523
1524       gtk_expander_set_label_widget (expander, child);
1525     }
1526
1527   g_object_notify (G_OBJECT (expander), "label");
1528 }
1529
1530 /**
1531  * gtk_expander_get_label:
1532  * @expander: a #GtkExpander
1533  *
1534  * Fetches the text from a label widget including any embedded
1535  * underlines indicating mnemonics and Pango markup, as set by
1536  * gtk_expander_set_label(). If the label text has not been set the
1537  * return value will be %NULL. This will be the case if you create an
1538  * empty button with gtk_button_new() to use as a container.
1539  *
1540  * Note that this function behaved differently in versions prior to
1541  * 2.14 and used to return the label text stripped of embedded
1542  * underlines indicating mnemonics and Pango markup. This problem can
1543  * be avoided by fetching the label text directly from the label
1544  * widget.
1545  *
1546  * Return value: The text of the label widget. This string is owned
1547  * by the widget and must not be modified or freed.
1548  *
1549  * Since: 2.4
1550  **/
1551 G_CONST_RETURN char *
1552 gtk_expander_get_label (GtkExpander *expander)
1553 {
1554   GtkExpanderPrivate *priv;
1555
1556   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1557
1558   priv = expander->priv;
1559
1560   if (GTK_IS_LABEL (priv->label_widget))
1561     return gtk_label_get_label (GTK_LABEL (priv->label_widget));
1562   else
1563     return NULL;
1564 }
1565
1566 /**
1567  * gtk_expander_set_use_underline:
1568  * @expander: a #GtkExpander
1569  * @use_underline: %TRUE if underlines in the text indicate mnemonics
1570  *
1571  * If true, an underline in the text of the expander label indicates
1572  * the next character should be used for the mnemonic accelerator key.
1573  *
1574  * Since: 2.4
1575  **/
1576 void
1577 gtk_expander_set_use_underline (GtkExpander *expander,
1578                                 gboolean     use_underline)
1579 {
1580   GtkExpanderPrivate *priv;
1581
1582   g_return_if_fail (GTK_IS_EXPANDER (expander));
1583
1584   priv = expander->priv;
1585
1586   use_underline = use_underline != FALSE;
1587
1588   if (priv->use_underline != use_underline)
1589     {
1590       priv->use_underline = use_underline;
1591
1592       if (GTK_IS_LABEL (priv->label_widget))
1593         gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), use_underline);
1594
1595       g_object_notify (G_OBJECT (expander), "use-underline");
1596     }
1597 }
1598
1599 /**
1600  * gtk_expander_get_use_underline:
1601  * @expander: a #GtkExpander
1602  *
1603  * Returns whether an embedded underline in the expander label indicates a
1604  * mnemonic. See gtk_expander_set_use_underline().
1605  *
1606  * Return value: %TRUE if an embedded underline in the expander label
1607  *               indicates the mnemonic accelerator keys.
1608  *
1609  * Since: 2.4
1610  **/
1611 gboolean
1612 gtk_expander_get_use_underline (GtkExpander *expander)
1613 {
1614   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1615
1616   return expander->priv->use_underline;
1617 }
1618
1619 /**
1620  * gtk_expander_set_use_markup:
1621  * @expander: a #GtkExpander
1622  * @use_markup: %TRUE if the label's text should be parsed for markup
1623  *
1624  * Sets whether the text of the label contains markup in <link
1625  * linkend="PangoMarkupFormat">Pango's text markup
1626  * language</link>. See gtk_label_set_markup().
1627  *
1628  * Since: 2.4
1629  **/
1630 void
1631 gtk_expander_set_use_markup (GtkExpander *expander,
1632                              gboolean     use_markup)
1633 {
1634   GtkExpanderPrivate *priv;
1635
1636   g_return_if_fail (GTK_IS_EXPANDER (expander));
1637
1638   priv = expander->priv;
1639
1640   use_markup = use_markup != FALSE;
1641
1642   if (priv->use_markup != use_markup)
1643     {
1644       priv->use_markup = use_markup;
1645
1646       if (GTK_IS_LABEL (priv->label_widget))
1647         gtk_label_set_use_markup (GTK_LABEL (priv->label_widget), use_markup);
1648
1649       g_object_notify (G_OBJECT (expander), "use-markup");
1650     }
1651 }
1652
1653 /**
1654  * gtk_expander_get_use_markup:
1655  * @expander: a #GtkExpander
1656  *
1657  * Returns whether the label's text is interpreted as marked up with
1658  * the <link linkend="PangoMarkupFormat">Pango text markup
1659  * language</link>. See gtk_expander_set_use_markup ().
1660  *
1661  * Return value: %TRUE if the label's text will be parsed for markup
1662  *
1663  * Since: 2.4
1664  **/
1665 gboolean
1666 gtk_expander_get_use_markup (GtkExpander *expander)
1667 {
1668   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1669
1670   return expander->priv->use_markup;
1671 }
1672
1673 /**
1674  * gtk_expander_set_label_widget:
1675  * @expander: a #GtkExpander
1676  * @label_widget: (allow-none): the new label widget
1677  *
1678  * Set the label widget for the expander. This is the widget
1679  * that will appear embedded alongside the expander arrow.
1680  *
1681  * Since: 2.4
1682  **/
1683 void
1684 gtk_expander_set_label_widget (GtkExpander *expander,
1685                                GtkWidget   *label_widget)
1686 {
1687   GtkExpanderPrivate *priv;
1688   GtkWidget          *widget;
1689
1690   g_return_if_fail (GTK_IS_EXPANDER (expander));
1691   g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
1692   g_return_if_fail (label_widget == NULL || label_widget->parent == NULL);
1693
1694   priv = expander->priv;
1695
1696   if (priv->label_widget == label_widget)
1697     return;
1698
1699   if (priv->label_widget)
1700     {
1701       gtk_widget_set_state (priv->label_widget, GTK_STATE_NORMAL);
1702       gtk_widget_unparent (priv->label_widget);
1703     }
1704
1705   priv->label_widget = label_widget;
1706   widget = GTK_WIDGET (expander);
1707
1708   if (label_widget)
1709     {
1710       priv->label_widget = label_widget;
1711
1712       gtk_widget_set_parent (label_widget, widget);
1713
1714       if (priv->prelight)
1715         gtk_widget_set_state (label_widget, GTK_STATE_PRELIGHT);
1716     }
1717
1718   if (gtk_widget_get_visible (widget))
1719     gtk_widget_queue_resize (widget);
1720
1721   g_object_freeze_notify (G_OBJECT (expander));
1722   g_object_notify (G_OBJECT (expander), "label-widget");
1723   g_object_notify (G_OBJECT (expander), "label");
1724   g_object_thaw_notify (G_OBJECT (expander));
1725 }
1726
1727 /**
1728  * gtk_expander_get_label_widget:
1729  * @expander: a #GtkExpander
1730  *
1731  * Retrieves the label widget for the frame. See
1732  * gtk_expander_set_label_widget().
1733  *
1734  * Return value: the label widget, or %NULL if there is none.
1735  * 
1736  * Since: 2.4
1737  **/
1738 GtkWidget *
1739 gtk_expander_get_label_widget (GtkExpander *expander)
1740 {
1741   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1742
1743   return expander->priv->label_widget;
1744 }