]> Pileus Git - ~andy/gtk/blob - gtk/gtkexpander.c
Merge branch 'master' into broadway
[~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
39 #define DEFAULT_EXPANDER_SIZE 10
40 #define DEFAULT_EXPANDER_SPACING 2
41
42 enum
43 {
44   PROP_0,
45   PROP_EXPANDED,
46   PROP_LABEL,
47   PROP_USE_UNDERLINE,
48   PROP_USE_MARKUP,
49   PROP_SPACING,
50   PROP_LABEL_WIDGET,
51   PROP_LABEL_FILL
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   guint             label_fill : 1;
70 };
71
72 static void gtk_expander_set_property (GObject          *object,
73                                        guint             prop_id,
74                                        const GValue     *value,
75                                        GParamSpec       *pspec);
76 static void gtk_expander_get_property (GObject          *object,
77                                        guint             prop_id,
78                                        GValue           *value,
79                                        GParamSpec       *pspec);
80
81 static void     gtk_expander_destroy        (GtkWidget        *widget);
82 static void     gtk_expander_realize        (GtkWidget        *widget);
83 static void     gtk_expander_unrealize      (GtkWidget        *widget);
84 static void     gtk_expander_size_allocate  (GtkWidget        *widget,
85                                              GtkAllocation    *allocation);
86 static void     gtk_expander_map            (GtkWidget        *widget);
87 static void     gtk_expander_unmap          (GtkWidget        *widget);
88 static gboolean gtk_expander_draw           (GtkWidget        *widget,
89                                              cairo_t          *cr);
90 static gboolean gtk_expander_button_press   (GtkWidget        *widget,
91                                              GdkEventButton   *event);
92 static gboolean gtk_expander_button_release (GtkWidget        *widget,
93                                              GdkEventButton   *event);
94 static gboolean gtk_expander_enter_notify   (GtkWidget        *widget,
95                                              GdkEventCrossing *event);
96 static gboolean gtk_expander_leave_notify   (GtkWidget        *widget,
97                                              GdkEventCrossing *event);
98 static gboolean gtk_expander_focus          (GtkWidget        *widget,
99                                              GtkDirectionType  direction);
100 static void     gtk_expander_grab_notify    (GtkWidget        *widget,
101                                              gboolean          was_grabbed);
102 static void     gtk_expander_state_changed  (GtkWidget        *widget,
103                                              GtkStateType      previous_state);
104 static gboolean gtk_expander_drag_motion    (GtkWidget        *widget,
105                                              GdkDragContext   *context,
106                                              gint              x,
107                                              gint              y,
108                                              guint             time);
109 static void     gtk_expander_drag_leave     (GtkWidget        *widget,
110                                              GdkDragContext   *context,
111                                              guint             time);
112
113 static void gtk_expander_add    (GtkContainer *container,
114                                  GtkWidget    *widget);
115 static void gtk_expander_remove (GtkContainer *container,
116                                  GtkWidget    *widget);
117 static void gtk_expander_forall (GtkContainer *container,
118                                  gboolean        include_internals,
119                                  GtkCallback     callback,
120                                  gpointer        callback_data);
121
122 static void gtk_expander_activate (GtkExpander *expander);
123
124 static void get_expander_bounds (GtkExpander  *expander,
125                                  GdkRectangle *rect);
126
127 /* GtkBuildable */
128 static void gtk_expander_buildable_init           (GtkBuildableIface *iface);
129 static void gtk_expander_buildable_add_child      (GtkBuildable *buildable,
130                                                    GtkBuilder   *builder,
131                                                    GObject      *child,
132                                                    const gchar  *type);
133
134
135 /* GtkWidget      */
136 static void  gtk_expander_get_preferred_width             (GtkWidget           *widget,
137                                                            gint                *minimum_size,
138                                                            gint                *natural_size);
139 static void  gtk_expander_get_preferred_height            (GtkWidget           *widget,
140                                                            gint                *minimum_size,
141                                                            gint                *natural_size);
142 static void  gtk_expander_get_preferred_height_for_width  (GtkWidget           *layout,
143                                                            gint                 width,
144                                                            gint                *minimum_height,
145                                                            gint                *natural_height);
146 static void  gtk_expander_get_preferred_width_for_height  (GtkWidget           *layout,
147                                                            gint                 width,
148                                                            gint                *minimum_height,
149                                                            gint                *natural_height);
150
151 G_DEFINE_TYPE_WITH_CODE (GtkExpander, gtk_expander, GTK_TYPE_BIN,
152                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
153                                                 gtk_expander_buildable_init))
154
155 static void
156 gtk_expander_class_init (GtkExpanderClass *klass)
157 {
158   GObjectClass *gobject_class;
159   GtkWidgetClass *widget_class;
160   GtkContainerClass *container_class;
161
162   gobject_class   = (GObjectClass *) klass;
163   widget_class    = (GtkWidgetClass *) klass;
164   container_class = (GtkContainerClass *) klass;
165
166   gobject_class->set_property = gtk_expander_set_property;
167   gobject_class->get_property = gtk_expander_get_property;
168
169   widget_class->destroy              = gtk_expander_destroy;
170   widget_class->realize              = gtk_expander_realize;
171   widget_class->unrealize            = gtk_expander_unrealize;
172   widget_class->size_allocate        = gtk_expander_size_allocate;
173   widget_class->map                  = gtk_expander_map;
174   widget_class->unmap                = gtk_expander_unmap;
175   widget_class->draw                 = gtk_expander_draw;
176   widget_class->button_press_event   = gtk_expander_button_press;
177   widget_class->button_release_event = gtk_expander_button_release;
178   widget_class->enter_notify_event   = gtk_expander_enter_notify;
179   widget_class->leave_notify_event   = gtk_expander_leave_notify;
180   widget_class->focus                = gtk_expander_focus;
181   widget_class->grab_notify          = gtk_expander_grab_notify;
182   widget_class->state_changed        = gtk_expander_state_changed;
183   widget_class->drag_motion          = gtk_expander_drag_motion;
184   widget_class->drag_leave           = gtk_expander_drag_leave;
185   widget_class->get_preferred_width            = gtk_expander_get_preferred_width;
186   widget_class->get_preferred_height           = gtk_expander_get_preferred_height;
187   widget_class->get_preferred_height_for_width = gtk_expander_get_preferred_height_for_width;
188   widget_class->get_preferred_width_for_height = gtk_expander_get_preferred_width_for_height;
189
190   container_class->add    = gtk_expander_add;
191   container_class->remove = gtk_expander_remove;
192   container_class->forall = gtk_expander_forall;
193
194   klass->activate = gtk_expander_activate;
195
196   g_type_class_add_private (klass, sizeof (GtkExpanderPrivate));
197
198   g_object_class_install_property (gobject_class,
199                                    PROP_EXPANDED,
200                                    g_param_spec_boolean ("expanded",
201                                                          P_("Expanded"),
202                                                          P_("Whether the expander has been opened to reveal the child widget"),
203                                                          FALSE,
204                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
205
206   g_object_class_install_property (gobject_class,
207                                    PROP_LABEL,
208                                    g_param_spec_string ("label",
209                                                         P_("Label"),
210                                                         P_("Text of the expander's label"),
211                                                         NULL,
212                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
213
214   g_object_class_install_property (gobject_class,
215                                    PROP_USE_UNDERLINE,
216                                    g_param_spec_boolean ("use-underline",
217                                                          P_("Use underline"),
218                                                          P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
219                                                          FALSE,
220                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
221
222   g_object_class_install_property (gobject_class,
223                                    PROP_USE_MARKUP,
224                                    g_param_spec_boolean ("use-markup",
225                                                          P_("Use markup"),
226                                                          P_("The text of the label includes XML markup. See pango_parse_markup()"),
227                                                          FALSE,
228                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
229
230   g_object_class_install_property (gobject_class,
231                                    PROP_SPACING,
232                                    g_param_spec_int ("spacing",
233                                                      P_("Spacing"),
234                                                      P_("Space to put between the label and the child"),
235                                                      0,
236                                                      G_MAXINT,
237                                                      0,
238                                                      GTK_PARAM_READWRITE));
239
240   g_object_class_install_property (gobject_class,
241                                    PROP_LABEL_WIDGET,
242                                    g_param_spec_object ("label-widget",
243                                                         P_("Label widget"),
244                                                         P_("A widget to display in place of the usual expander label"),
245                                                         GTK_TYPE_WIDGET,
246                                                         GTK_PARAM_READWRITE));
247
248   g_object_class_install_property (gobject_class,
249                                    PROP_LABEL_FILL,
250                                    g_param_spec_boolean ("label-fill",
251                                                          P_("Label fill"),
252                                                          P_("Whether the label widget should fill all available horizontal space"),
253                                                          FALSE,
254                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
255
256   gtk_widget_class_install_style_property (widget_class,
257                                            g_param_spec_int ("expander-size",
258                                                              P_("Expander Size"),
259                                                              P_("Size of the expander arrow"),
260                                                              0,
261                                                              G_MAXINT,
262                                                              DEFAULT_EXPANDER_SIZE,
263                                                              GTK_PARAM_READABLE));
264
265   gtk_widget_class_install_style_property (widget_class,
266                                            g_param_spec_int ("expander-spacing",
267                                                              P_("Indicator Spacing"),
268                                                              P_("Spacing around expander arrow"),
269                                                              0,
270                                                              G_MAXINT,
271                                                              DEFAULT_EXPANDER_SPACING,
272                                                              GTK_PARAM_READABLE));
273
274   widget_class->activate_signal =
275     g_signal_new (I_("activate"),
276                   G_TYPE_FROM_CLASS (gobject_class),
277                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
278                   G_STRUCT_OFFSET (GtkExpanderClass, activate),
279                   NULL, NULL,
280                   _gtk_marshal_VOID__VOID,
281                   G_TYPE_NONE, 0);
282 }
283
284 static void
285 gtk_expander_init (GtkExpander *expander)
286 {
287   GtkExpanderPrivate *priv;
288
289   expander->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (expander,
290                                                        GTK_TYPE_EXPANDER,
291                                                        GtkExpanderPrivate);
292
293   gtk_widget_set_can_focus (GTK_WIDGET (expander), TRUE);
294   gtk_widget_set_has_window (GTK_WIDGET (expander), FALSE);
295
296   priv->label_widget = NULL;
297   priv->event_window = NULL;
298   priv->spacing = 0;
299
300   priv->expander_style = GTK_EXPANDER_COLLAPSED;
301   priv->animation_timeout = 0;
302
303   priv->expanded = FALSE;
304   priv->use_underline = FALSE;
305   priv->use_markup = FALSE;
306   priv->button_down = FALSE;
307   priv->prelight = FALSE;
308   priv->label_fill = FALSE;
309   priv->expand_timer = 0;
310
311   gtk_drag_dest_set (GTK_WIDGET (expander), 0, NULL, 0, 0);
312   gtk_drag_dest_set_track_motion (GTK_WIDGET (expander), TRUE);
313 }
314
315 static void
316 gtk_expander_buildable_add_child (GtkBuildable  *buildable,
317                                   GtkBuilder    *builder,
318                                   GObject       *child,
319                                   const gchar   *type)
320 {
321   if (!type)
322     gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
323   else if (strcmp (type, "label") == 0)
324     gtk_expander_set_label_widget (GTK_EXPANDER (buildable), GTK_WIDGET (child));
325   else
326     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GTK_EXPANDER (buildable), type);
327 }
328
329 static void
330 gtk_expander_buildable_init (GtkBuildableIface *iface)
331 {
332   iface->add_child = gtk_expander_buildable_add_child;
333 }
334
335 static void
336 gtk_expander_set_property (GObject      *object,
337                            guint         prop_id,
338                            const GValue *value,
339                            GParamSpec   *pspec)
340 {
341   GtkExpander *expander = GTK_EXPANDER (object);
342                                                                                                              
343   switch (prop_id)
344     {
345     case PROP_EXPANDED:
346       gtk_expander_set_expanded (expander, g_value_get_boolean (value));
347       break;
348     case PROP_LABEL:
349       gtk_expander_set_label (expander, g_value_get_string (value));
350       break;
351     case PROP_USE_UNDERLINE:
352       gtk_expander_set_use_underline (expander, g_value_get_boolean (value));
353       break;
354     case PROP_USE_MARKUP:
355       gtk_expander_set_use_markup (expander, g_value_get_boolean (value));
356       break;
357     case PROP_SPACING:
358       gtk_expander_set_spacing (expander, g_value_get_int (value));
359       break;
360     case PROP_LABEL_WIDGET:
361       gtk_expander_set_label_widget (expander, g_value_get_object (value));
362       break;
363     case PROP_LABEL_FILL:
364       gtk_expander_set_label_fill (expander, g_value_get_boolean (value));
365       break;
366     default:
367       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
368       break;
369     }
370 }
371
372 static void
373 gtk_expander_get_property (GObject    *object,
374                            guint       prop_id,
375                            GValue     *value,
376                            GParamSpec *pspec)
377 {
378   GtkExpander *expander = GTK_EXPANDER (object);
379   GtkExpanderPrivate *priv = expander->priv;
380
381   switch (prop_id)
382     {
383     case PROP_EXPANDED:
384       g_value_set_boolean (value, priv->expanded);
385       break;
386     case PROP_LABEL:
387       g_value_set_string (value, gtk_expander_get_label (expander));
388       break;
389     case PROP_USE_UNDERLINE:
390       g_value_set_boolean (value, priv->use_underline);
391       break;
392     case PROP_USE_MARKUP:
393       g_value_set_boolean (value, priv->use_markup);
394       break;
395     case PROP_SPACING:
396       g_value_set_int (value, priv->spacing);
397       break;
398     case PROP_LABEL_WIDGET:
399       g_value_set_object (value,
400                           priv->label_widget ?
401                           G_OBJECT (priv->label_widget) : NULL);
402       break;
403     case PROP_LABEL_FILL:
404       g_value_set_boolean (value, priv->label_fill);
405       break;
406     default:
407       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
408       break;
409     }
410 }
411
412 static void
413 gtk_expander_destroy (GtkWidget *widget)
414 {
415   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
416
417   if (priv->animation_timeout)
418     {
419       g_source_remove (priv->animation_timeout);
420       priv->animation_timeout = 0;
421     }
422
423   GTK_WIDGET_CLASS (gtk_expander_parent_class)->destroy (widget);
424 }
425
426 static void
427 gtk_expander_realize (GtkWidget *widget)
428 {
429   GtkAllocation allocation;
430   GtkExpanderPrivate *priv;
431   GdkWindow *window;
432   GdkWindowAttr attributes;
433   gint attributes_mask;
434   gint border_width;
435   GdkRectangle expander_rect;
436   gint label_height;
437
438   priv = GTK_EXPANDER (widget)->priv;
439
440   gtk_widget_set_realized (widget, TRUE);
441
442   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
443
444   get_expander_bounds (GTK_EXPANDER (widget), &expander_rect);
445   
446   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
447     {
448       GtkRequisition label_requisition;
449
450       gtk_widget_get_preferred_size (priv->label_widget,
451                                      &label_requisition, NULL);
452       label_height = label_requisition.height;
453     }
454   else
455     label_height = 0;
456
457   gtk_widget_get_allocation (widget, &allocation);
458
459   attributes.window_type = GDK_WINDOW_CHILD;
460   attributes.x = allocation.x + border_width;
461   attributes.y = allocation.y + border_width;
462   attributes.width = MAX (allocation.width - 2 * border_width, 1);
463   attributes.height = MAX (expander_rect.height, label_height - 2 * border_width);
464   attributes.wclass = GDK_INPUT_ONLY;
465   attributes.event_mask = gtk_widget_get_events (widget)     |
466                                 GDK_BUTTON_PRESS_MASK        |
467                                 GDK_BUTTON_RELEASE_MASK      |
468                                 GDK_ENTER_NOTIFY_MASK        |
469                                 GDK_LEAVE_NOTIFY_MASK;
470
471   attributes_mask = GDK_WA_X | GDK_WA_Y;
472
473   window = gtk_widget_get_parent_window (widget);
474   gtk_widget_set_window (widget, window);
475   g_object_ref (window);
476
477   priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
478                                        &attributes, attributes_mask);
479   gdk_window_set_user_data (priv->event_window, widget);
480
481   gtk_widget_style_attach (widget);
482 }
483
484 static void
485 gtk_expander_unrealize (GtkWidget *widget)
486 {
487   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
488
489   if (priv->event_window)
490     {
491       gdk_window_set_user_data (priv->event_window, NULL);
492       gdk_window_destroy (priv->event_window);
493       priv->event_window = NULL;
494     }
495
496   GTK_WIDGET_CLASS (gtk_expander_parent_class)->unrealize (widget);
497 }
498
499 static void
500 get_expander_bounds (GtkExpander  *expander,
501                      GdkRectangle *rect)
502 {
503   GtkAllocation allocation;
504   GtkWidget *widget;
505   GtkExpanderPrivate *priv;
506   gint border_width;
507   gint expander_size;
508   gint expander_spacing;
509   gboolean interior_focus;
510   gint focus_width;
511   gint focus_pad;
512   gboolean ltr;
513
514   widget = GTK_WIDGET (expander);
515   priv = expander->priv;
516
517   gtk_widget_get_allocation (widget, &allocation);
518
519   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
520
521   gtk_widget_style_get (widget,
522                         "interior-focus", &interior_focus,
523                         "focus-line-width", &focus_width,
524                         "focus-padding", &focus_pad,
525                         "expander-size", &expander_size,
526                         "expander-spacing", &expander_spacing,
527                         NULL);
528
529   ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
530
531   rect->x = allocation.x + border_width;
532   rect->y = allocation.y + border_width;
533
534   if (ltr)
535     rect->x += expander_spacing;
536   else
537     rect->x += allocation.width - 2 * border_width -
538                expander_spacing - expander_size;
539
540   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
541     {
542       GtkAllocation label_allocation;
543
544       gtk_widget_get_allocation (priv->label_widget, &label_allocation);
545
546       if (expander_size < label_allocation.height)
547         rect->y += focus_width + focus_pad + (label_allocation.height - expander_size) / 2;
548       else
549         rect->y += expander_spacing;
550     }
551   else
552     {
553       rect->y += expander_spacing;
554     }
555
556   if (!interior_focus)
557     {
558       if (ltr)
559         rect->x += focus_width + focus_pad;
560       else
561         rect->x -= focus_width + focus_pad;
562       rect->y += focus_width + focus_pad;
563     }
564
565   rect->width = rect->height = expander_size;
566 }
567
568 static void
569 gtk_expander_size_allocate (GtkWidget     *widget,
570                             GtkAllocation *allocation)
571 {
572   GtkExpander *expander;
573   GtkWidget *child;
574   GtkExpanderPrivate *priv;
575   gboolean child_visible = FALSE;
576   guint border_width;
577   gint expander_size;
578   gint expander_spacing;
579   gboolean interior_focus;
580   gint focus_width;
581   gint focus_pad;
582   gint label_height, top_min_height;
583   gint label_xpad, label_xoffset;
584   gint child_ypad, child_yoffset;
585
586   expander = GTK_EXPANDER (widget);
587   child    = gtk_bin_get_child (GTK_BIN (widget));
588   priv     = expander->priv;
589
590   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
591
592   gtk_widget_set_allocation (widget, allocation);
593
594   gtk_widget_style_get (widget,
595                         "interior-focus", &interior_focus,
596                         "focus-line-width", &focus_width,
597                         "focus-padding", &focus_pad,
598                         "expander-size", &expander_size,
599                         "expander-spacing", &expander_spacing,
600                         NULL);
601
602
603   /* Calculate some offsets/padding first */
604   label_xoffset = border_width + expander_size + focus_width + 2 * expander_spacing + focus_pad;
605   label_xpad = 2 * border_width + expander_size + 2 * focus_width + 2 * expander_spacing + 2 * focus_pad;
606
607   child_yoffset  = border_width + priv->spacing + (interior_focus ? 0 : 2 * focus_width + 2 * focus_pad);
608   child_ypad     = 2 * border_width + priv->spacing + (interior_focus ? 0 : 2 * focus_width + 2 * focus_pad);
609   top_min_height = 2 * expander_spacing + expander_size;
610
611   child_visible = (child && gtk_widget_get_child_visible (child));
612
613   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
614     {
615       GtkAllocation label_allocation;
616       gint          natural_label_width;
617       gboolean ltr;
618
619       gtk_widget_get_preferred_width (priv->label_widget, NULL, &natural_label_width);
620
621       if (priv->label_fill)
622         label_allocation.width = allocation->width - label_xpad;
623       else
624         label_allocation.width = MIN (natural_label_width, allocation->width - label_xpad);
625       label_allocation.width = MAX (label_allocation.width, 1);
626
627       /* We distribute the minimum height to the label widget and prioritize
628        * the child widget giving it the remaining height */
629       gtk_widget_get_preferred_height_for_width (priv->label_widget,
630                                                  label_allocation.width, &label_height, NULL);
631
632       ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
633
634       if (priv->label_fill)
635         label_allocation.x = allocation->x + label_xoffset;
636       else if (ltr)
637         label_allocation.x = allocation->x + label_xoffset;
638       else
639         label_allocation.x = allocation->x + allocation->width -
640                              (label_allocation.width + label_xoffset);
641
642       label_allocation.y = allocation->y + border_width + focus_width + focus_pad;
643       label_allocation.height = MIN (label_height,
644                                      allocation->height - 2 * border_width -
645                                      2 * focus_width - 2 * focus_pad -
646                                      (child_visible ? priv->spacing : 0));
647       label_allocation.height = MAX (label_allocation.height, 1);
648
649       gtk_widget_size_allocate (priv->label_widget, &label_allocation);
650
651       label_height = label_allocation.height;
652     }
653   else
654     {
655       label_height = 0;
656     }
657
658   if (gtk_widget_get_realized (widget))
659     {
660       GdkRectangle rect;
661
662       get_expander_bounds (expander, &rect);
663
664       gdk_window_move_resize (priv->event_window,
665                               allocation->x + border_width,
666                               allocation->y + border_width,
667                               MAX (allocation->width - 2 * border_width, 1),
668                               MAX (rect.height, label_height - 2 * border_width));
669     }
670
671   if (child_visible)
672     {
673       GtkAllocation child_allocation;
674       gint top_height;
675
676       top_height = MAX (top_min_height,
677                         label_height + (interior_focus ? 2 * focus_width + 2 * focus_pad : 0));
678
679       child_allocation.x = allocation->x + border_width;
680       child_allocation.y = allocation->y + top_height + child_yoffset;
681
682       child_allocation.width = MAX (allocation->width - 2 * border_width, 1);
683       child_allocation.height = allocation->height - top_height - child_ypad;
684       child_allocation.height = MAX (child_allocation.height, 1);
685
686       gtk_widget_size_allocate (child, &child_allocation);
687     }
688 }
689
690 static void
691 gtk_expander_map (GtkWidget *widget)
692 {
693   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
694
695   if (priv->label_widget)
696     gtk_widget_map (priv->label_widget);
697
698   GTK_WIDGET_CLASS (gtk_expander_parent_class)->map (widget);
699
700   if (priv->event_window)
701     gdk_window_show (priv->event_window);
702 }
703
704 static void
705 gtk_expander_unmap (GtkWidget *widget)
706 {
707   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
708
709   if (priv->event_window)
710     gdk_window_hide (priv->event_window);
711
712   GTK_WIDGET_CLASS (gtk_expander_parent_class)->unmap (widget);
713
714   if (priv->label_widget)
715     gtk_widget_unmap (priv->label_widget);
716 }
717
718 static void
719 gtk_expander_paint_prelight (GtkExpander *expander, cairo_t *cr)
720 {
721   GtkAllocation allocation;
722   GtkWidget *widget;
723   GtkContainer *container;
724   GtkExpanderPrivate *priv;
725   GdkRectangle area;
726   gboolean interior_focus;
727   int focus_width;
728   int focus_pad;
729   int expander_size;
730   int expander_spacing;
731   guint border_width;
732
733   priv = expander->priv;
734   widget = GTK_WIDGET (expander);
735   container = GTK_CONTAINER (expander);
736
737   gtk_widget_style_get (widget,
738                         "interior-focus", &interior_focus,
739                         "focus-line-width", &focus_width,
740                         "focus-padding", &focus_pad,
741                         "expander-size", &expander_size,
742                         "expander-spacing", &expander_spacing,
743                         NULL);
744
745   gtk_widget_get_allocation (widget, &allocation);
746
747   border_width = gtk_container_get_border_width (container);
748   area.x = border_width;
749   area.y = border_width;
750   area.width = allocation.width - (2 * border_width);
751
752   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
753     {
754       GtkAllocation label_widget_allocation;
755
756       gtk_widget_get_allocation (priv->label_widget, &label_widget_allocation);
757       area.height = label_widget_allocation.height;
758     }
759   else
760     area.height = 0;
761
762   area.height += interior_focus ? (focus_width + focus_pad) * 2 : 0;
763   area.height = MAX (area.height, expander_size + 2 * expander_spacing);
764   area.height += !interior_focus ? (focus_width + focus_pad) * 2 : 0;
765
766   gtk_paint_flat_box (gtk_widget_get_style (widget),
767                       cr,
768                       GTK_STATE_PRELIGHT,
769                       GTK_SHADOW_ETCHED_OUT,
770                       widget, "expander",
771                       area.x, area.y,
772                       area.width, area.height);
773 }
774
775 static void
776 gtk_expander_paint (GtkExpander *expander, cairo_t *cr)
777 {
778   GtkWidget *widget;
779   GdkRectangle clip;
780   GtkAllocation allocation;
781   GtkStateType state;
782
783   widget = GTK_WIDGET (expander);
784
785   get_expander_bounds (expander, &clip);
786   gtk_widget_get_allocation (widget, &allocation);
787
788   state = gtk_widget_get_state (widget);
789   if (expander->priv->prelight)
790     {
791       state = GTK_STATE_PRELIGHT;
792
793       gtk_expander_paint_prelight (expander, cr);
794     }
795
796   gtk_paint_expander (gtk_widget_get_style (widget),
797                       cr,
798                       state,
799                       widget,
800                       "expander",
801                       clip.x + clip.width / 2 - allocation.x,
802                       clip.y + clip.height / 2 - allocation.y,
803                       expander->priv->expander_style);
804 }
805
806 static void
807 gtk_expander_paint_focus (GtkExpander *expander,
808                           cairo_t     *cr)
809 {
810   GtkWidget *widget;
811   GtkExpanderPrivate *priv;
812   GdkRectangle rect;
813   gint x, y, width, height;
814   gboolean interior_focus;
815   gint border_width;
816   gint focus_width;
817   gint focus_pad;
818   gint expander_size;
819   gint expander_spacing;
820   gboolean ltr;
821   GtkAllocation allocation;
822
823   widget = GTK_WIDGET (expander);
824   priv = expander->priv;
825
826   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
827   gtk_widget_get_allocation (widget, &allocation);
828
829   gtk_widget_style_get (widget,
830                         "interior-focus", &interior_focus,
831                         "focus-line-width", &focus_width,
832                         "focus-padding", &focus_pad,
833                         "expander-size", &expander_size,
834                         "expander-spacing", &expander_spacing,
835                         NULL);
836
837   ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
838   
839   width = height = 0;
840
841   if (priv->label_widget)
842     {
843       if (gtk_widget_get_visible (priv->label_widget))
844         {
845           GtkAllocation label_allocation;
846
847           gtk_widget_get_allocation (priv->label_widget, &label_allocation);
848           width  = label_allocation.width;
849           height = label_allocation.height;
850         }
851
852       width  += 2 * focus_pad + 2 * focus_width;
853       height += 2 * focus_pad + 2 * focus_width;
854
855       x = border_width;
856       y = border_width;
857
858       if (ltr)
859         {
860           if (interior_focus)
861             x += expander_spacing * 2 + expander_size;
862         }
863       else
864         {
865           x += allocation.width - 2 * border_width
866             - expander_spacing * 2 - expander_size - width;
867         }
868
869       if (!interior_focus)
870         {
871           width += expander_size + 2 * expander_spacing;
872           height = MAX (height, expander_size + 2 * expander_spacing);
873         }
874     }
875   else
876     {
877       get_expander_bounds (expander, &rect);
878
879       x = rect.x - allocation.x - focus_pad;
880       y = rect.y - allocation.y - focus_pad;
881       width = rect.width + 2 * focus_pad;
882       height = rect.height + 2 * focus_pad;
883     }
884
885   gtk_paint_focus (gtk_widget_get_style (widget),
886                    cr,
887                    gtk_widget_get_state (widget),
888                    widget, "expander",
889                    x, y, width, height);
890 }
891
892 static gboolean
893 gtk_expander_draw (GtkWidget *widget,
894                    cairo_t   *cr)
895 {
896   GtkExpander *expander = GTK_EXPANDER (widget);
897
898   gtk_expander_paint (expander, cr);
899
900   if (gtk_widget_has_focus (widget))
901     gtk_expander_paint_focus (expander, cr);
902
903   GTK_WIDGET_CLASS (gtk_expander_parent_class)->draw (widget, cr);
904
905   return FALSE;
906 }
907
908 static gboolean
909 gtk_expander_button_press (GtkWidget      *widget,
910                            GdkEventButton *event)
911 {
912   GtkExpander *expander = GTK_EXPANDER (widget);
913
914   if (event->button == 1 && event->window == expander->priv->event_window)
915     {
916       expander->priv->button_down = TRUE;
917       return TRUE;
918     }
919
920   return FALSE;
921 }
922
923 static gboolean
924 gtk_expander_button_release (GtkWidget      *widget,
925                              GdkEventButton *event)
926 {
927   GtkExpander *expander = GTK_EXPANDER (widget);
928
929   if (event->button == 1 && expander->priv->button_down)
930     {
931       gtk_widget_activate (widget);
932       expander->priv->button_down = FALSE;
933       return TRUE;
934     }
935
936   return FALSE;
937 }
938
939 static void
940 gtk_expander_grab_notify (GtkWidget *widget,
941                           gboolean   was_grabbed)
942 {
943   if (!was_grabbed)
944     GTK_EXPANDER (widget)->priv->button_down = FALSE;
945 }
946
947 static void
948 gtk_expander_state_changed (GtkWidget    *widget,
949                             GtkStateType  previous_state)
950 {
951   if (!gtk_widget_is_sensitive (widget))
952     GTK_EXPANDER (widget)->priv->button_down = FALSE;
953 }
954
955 static void
956 gtk_expander_redraw_expander (GtkExpander *expander)
957 {
958   GtkAllocation allocation;
959   GtkWidget *widget = GTK_WIDGET (expander);
960
961   if (gtk_widget_get_realized (widget))
962     {
963       gtk_widget_get_allocation (widget, &allocation);
964       gdk_window_invalidate_rect (gtk_widget_get_window (widget), &allocation, FALSE);
965     }
966 }
967
968 static gboolean
969 gtk_expander_enter_notify (GtkWidget        *widget,
970                            GdkEventCrossing *event)
971 {
972   GtkExpander *expander = GTK_EXPANDER (widget);
973
974   if (event->window == expander->priv->event_window &&
975       event->detail != GDK_NOTIFY_INFERIOR)
976     {
977       expander->priv->prelight = TRUE;
978
979       if (expander->priv->label_widget)
980         gtk_widget_set_state_flags (expander->priv->label_widget,
981                                     GTK_STATE_FLAG_PRELIGHT,
982                                     FALSE);
983
984       gtk_expander_redraw_expander (expander);
985     }
986
987   return FALSE;
988 }
989
990 static gboolean
991 gtk_expander_leave_notify (GtkWidget        *widget,
992                            GdkEventCrossing *event)
993 {
994   GtkExpander *expander = GTK_EXPANDER (widget);
995
996   if (event->window == expander->priv->event_window &&
997       event->detail != GDK_NOTIFY_INFERIOR)
998     {
999       expander->priv->prelight = FALSE;
1000
1001       if (expander->priv->label_widget)
1002         gtk_widget_unset_state_flags (expander->priv->label_widget,
1003                                       GTK_STATE_FLAG_PRELIGHT);
1004
1005       gtk_expander_redraw_expander (expander);
1006     }
1007
1008   return FALSE;
1009 }
1010
1011 static gboolean
1012 expand_timeout (gpointer data)
1013 {
1014   GtkExpander *expander = GTK_EXPANDER (data);
1015   GtkExpanderPrivate *priv = expander->priv;
1016
1017   priv->expand_timer = 0;
1018   gtk_expander_set_expanded (expander, TRUE);
1019
1020   return FALSE;
1021 }
1022
1023 static gboolean
1024 gtk_expander_drag_motion (GtkWidget        *widget,
1025                           GdkDragContext   *context,
1026                           gint              x,
1027                           gint              y,
1028                           guint             time)
1029 {
1030   GtkExpander *expander = GTK_EXPANDER (widget);
1031   GtkExpanderPrivate *priv = expander->priv;
1032
1033   if (!priv->expanded && !priv->expand_timer)
1034     {
1035       GtkSettings *settings;
1036       guint timeout;
1037
1038       settings = gtk_widget_get_settings (widget);
1039       g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
1040
1041       priv->expand_timer = gdk_threads_add_timeout (timeout, (GSourceFunc) expand_timeout, expander);
1042     }
1043
1044   return TRUE;
1045 }
1046
1047 static void
1048 gtk_expander_drag_leave (GtkWidget      *widget,
1049                          GdkDragContext *context,
1050                          guint           time)
1051 {
1052   GtkExpander *expander = GTK_EXPANDER (widget);
1053   GtkExpanderPrivate *priv = expander->priv;
1054
1055   if (priv->expand_timer)
1056     {
1057       g_source_remove (priv->expand_timer);
1058       priv->expand_timer = 0;
1059     }
1060 }
1061
1062 typedef enum
1063 {
1064   FOCUS_NONE,
1065   FOCUS_WIDGET,
1066   FOCUS_LABEL,
1067   FOCUS_CHILD
1068 } FocusSite;
1069
1070 static gboolean
1071 focus_current_site (GtkExpander      *expander,
1072                     GtkDirectionType  direction)
1073 {
1074   GtkWidget *current_focus;
1075
1076   current_focus = gtk_container_get_focus_child (GTK_CONTAINER (expander));
1077
1078   if (!current_focus)
1079     return FALSE;
1080
1081   return gtk_widget_child_focus (current_focus, direction);
1082 }
1083
1084 static gboolean
1085 focus_in_site (GtkExpander      *expander,
1086                FocusSite         site,
1087                GtkDirectionType  direction)
1088 {
1089   switch (site)
1090     {
1091     case FOCUS_WIDGET:
1092       gtk_widget_grab_focus (GTK_WIDGET (expander));
1093       return TRUE;
1094     case FOCUS_LABEL:
1095       if (expander->priv->label_widget)
1096         return gtk_widget_child_focus (expander->priv->label_widget, direction);
1097       else
1098         return FALSE;
1099     case FOCUS_CHILD:
1100       {
1101         GtkWidget *child = gtk_bin_get_child (GTK_BIN (expander));
1102
1103         if (child && gtk_widget_get_child_visible (child))
1104           return gtk_widget_child_focus (child, direction);
1105         else
1106           return FALSE;
1107       }
1108     case FOCUS_NONE:
1109       break;
1110     }
1111
1112   g_assert_not_reached ();
1113   return FALSE;
1114 }
1115
1116 static FocusSite
1117 get_next_site (GtkExpander      *expander,
1118                FocusSite         site,
1119                GtkDirectionType  direction)
1120 {
1121   gboolean ltr;
1122
1123   ltr = gtk_widget_get_direction (GTK_WIDGET (expander)) != GTK_TEXT_DIR_RTL;
1124
1125   switch (site)
1126     {
1127     case FOCUS_NONE:
1128       switch (direction)
1129         {
1130         case GTK_DIR_TAB_BACKWARD:
1131         case GTK_DIR_LEFT:
1132         case GTK_DIR_UP:
1133           return FOCUS_CHILD;
1134         case GTK_DIR_TAB_FORWARD:
1135         case GTK_DIR_DOWN:
1136         case GTK_DIR_RIGHT:
1137           return FOCUS_WIDGET;
1138         }
1139     case FOCUS_WIDGET:
1140       switch (direction)
1141         {
1142         case GTK_DIR_TAB_BACKWARD:
1143         case GTK_DIR_UP:
1144           return FOCUS_NONE;
1145         case GTK_DIR_LEFT:
1146           return ltr ? FOCUS_NONE : FOCUS_LABEL;
1147         case GTK_DIR_TAB_FORWARD:
1148         case GTK_DIR_DOWN:
1149           return FOCUS_LABEL;
1150         case GTK_DIR_RIGHT:
1151           return ltr ? FOCUS_LABEL : FOCUS_NONE;
1152           break;
1153         }
1154     case FOCUS_LABEL:
1155       switch (direction)
1156         {
1157         case GTK_DIR_TAB_BACKWARD:
1158         case GTK_DIR_UP:
1159           return FOCUS_WIDGET;
1160         case GTK_DIR_LEFT:
1161           return ltr ? FOCUS_WIDGET : FOCUS_CHILD;
1162         case GTK_DIR_TAB_FORWARD:
1163         case GTK_DIR_DOWN:
1164           return FOCUS_CHILD;
1165         case GTK_DIR_RIGHT:
1166           return ltr ? FOCUS_CHILD : FOCUS_WIDGET;
1167           break;
1168         }
1169     case FOCUS_CHILD:
1170       switch (direction)
1171         {
1172         case GTK_DIR_TAB_BACKWARD:
1173         case GTK_DIR_LEFT:
1174         case GTK_DIR_UP:
1175           return FOCUS_LABEL;
1176         case GTK_DIR_TAB_FORWARD:
1177         case GTK_DIR_DOWN:
1178         case GTK_DIR_RIGHT:
1179           return FOCUS_NONE;
1180         }
1181     }
1182
1183   g_assert_not_reached ();
1184   return FOCUS_NONE;
1185 }
1186
1187 static gboolean
1188 gtk_expander_focus (GtkWidget        *widget,
1189                     GtkDirectionType  direction)
1190 {
1191   GtkExpander *expander = GTK_EXPANDER (widget);
1192   
1193   if (!focus_current_site (expander, direction))
1194     {
1195       GtkWidget *old_focus_child;
1196       gboolean widget_is_focus;
1197       FocusSite site = FOCUS_NONE;
1198       
1199       widget_is_focus = gtk_widget_is_focus (widget);
1200       old_focus_child = gtk_container_get_focus_child (GTK_CONTAINER (widget));
1201       
1202       if (old_focus_child && old_focus_child == expander->priv->label_widget)
1203         site = FOCUS_LABEL;
1204       else if (old_focus_child)
1205         site = FOCUS_CHILD;
1206       else if (widget_is_focus)
1207         site = FOCUS_WIDGET;
1208
1209       while ((site = get_next_site (expander, site, direction)) != FOCUS_NONE)
1210         {
1211           if (focus_in_site (expander, site, direction))
1212             return TRUE;
1213         }
1214
1215       return FALSE;
1216     }
1217
1218   return TRUE;
1219 }
1220
1221 static void
1222 gtk_expander_add (GtkContainer *container,
1223                   GtkWidget    *widget)
1224 {
1225   GTK_CONTAINER_CLASS (gtk_expander_parent_class)->add (container, widget);
1226
1227   gtk_widget_set_child_visible (widget, GTK_EXPANDER (container)->priv->expanded);
1228   gtk_widget_queue_resize (GTK_WIDGET (container));
1229 }
1230
1231 static void
1232 gtk_expander_remove (GtkContainer *container,
1233                      GtkWidget    *widget)
1234 {
1235   GtkExpander *expander = GTK_EXPANDER (container);
1236
1237   if (GTK_EXPANDER (expander)->priv->label_widget == widget)
1238     gtk_expander_set_label_widget (expander, NULL);
1239   else
1240     GTK_CONTAINER_CLASS (gtk_expander_parent_class)->remove (container, widget);
1241 }
1242
1243 static void
1244 gtk_expander_forall (GtkContainer *container,
1245                      gboolean      include_internals,
1246                      GtkCallback   callback,
1247                      gpointer      callback_data)
1248 {
1249   GtkBin *bin = GTK_BIN (container);
1250   GtkExpanderPrivate *priv = GTK_EXPANDER (container)->priv;
1251   GtkWidget *child;
1252
1253   child = gtk_bin_get_child (bin);
1254   if (child)
1255     (* callback) (child, callback_data);
1256
1257   if (priv->label_widget)
1258     (* callback) (priv->label_widget, callback_data);
1259 }
1260
1261 static void
1262 gtk_expander_activate (GtkExpander *expander)
1263 {
1264   gtk_expander_set_expanded (expander, !expander->priv->expanded);
1265 }
1266
1267
1268 static void     
1269 gtk_expander_get_preferred_width (GtkWidget *widget,
1270                                   gint      *minimum_size,
1271                                   gint      *natural_size)
1272 {
1273   GtkExpander *expander;
1274   GtkWidget *child;
1275   GtkExpanderPrivate *priv;
1276   gint border_width;
1277   gint expander_size;
1278   gint expander_spacing;
1279   gboolean interior_focus;
1280   gint focus_width;
1281   gint focus_pad;
1282
1283   child = gtk_bin_get_child (GTK_BIN (widget));
1284   expander = GTK_EXPANDER (widget);
1285   priv = expander->priv;
1286
1287   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1288
1289   gtk_widget_style_get (GTK_WIDGET (widget),
1290                         "interior-focus", &interior_focus,
1291                         "focus-line-width", &focus_width,
1292                         "focus-padding", &focus_pad,
1293                         "expander-size", &expander_size,
1294                         "expander-spacing", &expander_spacing,
1295                         NULL);
1296
1297   *minimum_size = *natural_size = 
1298     expander_size + 2 * expander_spacing +
1299     2 * focus_width + 2 * focus_pad;
1300
1301   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1302     {
1303       gint label_min, label_nat;
1304
1305       gtk_widget_get_preferred_width (priv->label_widget, 
1306                                       &label_min, &label_nat);
1307
1308       *minimum_size += label_min;
1309       *natural_size += label_nat;
1310     }
1311
1312   if (child && gtk_widget_get_child_visible (child))
1313     {
1314       gint child_min, child_nat;
1315
1316       gtk_widget_get_preferred_width (child, 
1317                                       &child_min, &child_nat);
1318
1319       *minimum_size = MAX (*minimum_size, child_min);
1320       *natural_size = MAX (*natural_size, child_nat);
1321
1322     }
1323
1324   *minimum_size += 2 * border_width;
1325   *natural_size += 2 * border_width;
1326 }
1327
1328 static void
1329 gtk_expander_get_preferred_height (GtkWidget *widget,
1330                                    gint      *minimum_size,
1331                                    gint      *natural_size)
1332 {  
1333   GtkExpander *expander;
1334   GtkWidget *child;
1335   GtkExpanderPrivate *priv;
1336   gint border_width;
1337   gint expander_size;
1338   gint expander_spacing;
1339   gboolean interior_focus;
1340   gint focus_width;
1341   gint focus_pad;
1342
1343   child = gtk_bin_get_child (GTK_BIN (widget));
1344   expander = GTK_EXPANDER (widget);
1345   priv = expander->priv;
1346
1347   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1348
1349   gtk_widget_style_get (GTK_WIDGET (widget),
1350                         "interior-focus", &interior_focus,
1351                         "focus-line-width", &focus_width,
1352                         "focus-padding", &focus_pad,
1353                         "expander-size", &expander_size,
1354                         "expander-spacing", &expander_spacing,
1355                         NULL);
1356
1357   *minimum_size = *natural_size = 
1358     interior_focus ? (2 * focus_width + 2 * focus_pad) : 0;
1359
1360
1361   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1362     {
1363       gint label_min, label_nat;
1364
1365       gtk_widget_get_preferred_height (priv->label_widget, 
1366                                        &label_min, &label_nat);
1367       
1368       *minimum_size += label_min;
1369       *natural_size += label_nat;
1370     }
1371
1372   *minimum_size = MAX (*minimum_size, expander_size + 2 * expander_spacing);
1373   *natural_size = MAX (*natural_size, *minimum_size);
1374
1375   if (!interior_focus)
1376     {
1377       gint extra = 2 * focus_width + 2 * focus_pad;
1378       *minimum_size += extra;
1379       *natural_size += extra;
1380     }
1381
1382   if (child && gtk_widget_get_child_visible (child))
1383     {
1384       gint child_min, child_nat;
1385
1386       gtk_widget_get_preferred_height (child, 
1387                                        &child_min, &child_nat);
1388
1389       *minimum_size += child_min + priv->spacing;
1390       *natural_size += child_nat + priv->spacing;
1391
1392     }
1393
1394   *minimum_size += 2 * border_width;
1395   *natural_size += 2 * border_width;
1396 }
1397
1398 static void
1399 gtk_expander_get_preferred_height_for_width (GtkWidget *widget,
1400                                              gint       width,
1401                                              gint      *minimum_height,
1402                                              gint      *natural_height)
1403 {
1404   GtkExpander *expander;
1405   GtkWidget *child;
1406   GtkExpanderPrivate *priv;
1407   gint border_width;
1408   gint expander_size;
1409   gint expander_spacing;
1410   gboolean interior_focus;
1411   gint focus_width;
1412   gint focus_pad;
1413   gint label_xpad;
1414
1415   child = gtk_bin_get_child (GTK_BIN (widget));
1416   expander = GTK_EXPANDER (widget);
1417   priv = expander->priv;
1418
1419   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1420
1421   gtk_widget_style_get (GTK_WIDGET (widget),
1422                         "interior-focus", &interior_focus,
1423                         "focus-line-width", &focus_width,
1424                         "focus-padding", &focus_pad,
1425                         "expander-size", &expander_size,
1426                         "expander-spacing", &expander_spacing,
1427                         NULL);
1428
1429   label_xpad = 2 * border_width + expander_size + 2 * expander_spacing - 2 * focus_width + 2 * focus_pad;
1430
1431   *minimum_height = *natural_height = 
1432     interior_focus ? (2 * focus_width + 2 * focus_pad) : 0;
1433
1434
1435   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1436     {
1437       gint label_min, label_nat;
1438
1439       gtk_widget_get_preferred_height_for_width (priv->label_widget, 
1440                                                  MAX (width - label_xpad, 1), 
1441                                                  &label_min, &label_nat);
1442       
1443       *minimum_height += label_min;
1444       *natural_height += label_nat;
1445     }
1446
1447   *minimum_height = MAX (*minimum_height, expander_size + 2 * expander_spacing);
1448   *natural_height = MAX (*natural_height, *minimum_height);
1449
1450   if (!interior_focus)
1451     {
1452       gint extra = 2 * focus_width + 2 * focus_pad;
1453       *minimum_height += extra;
1454       *natural_height += extra;
1455     }
1456
1457   if (child && gtk_widget_get_child_visible (child))
1458     {
1459       gint child_min, child_nat;
1460
1461       gtk_widget_get_preferred_height_for_width (child, 
1462                                                  MAX (width - 2 * border_width, 1), 
1463                                                  &child_min, &child_nat);
1464
1465       *minimum_height += child_min + priv->spacing;
1466       *natural_height += child_nat + priv->spacing;
1467     }
1468
1469   *minimum_height += 2 * border_width;
1470   *natural_height += 2 * border_width;
1471 }
1472
1473 static void
1474 gtk_expander_get_preferred_width_for_height (GtkWidget *widget,
1475                                              gint       height,
1476                                              gint      *minimum_width,
1477                                              gint      *natural_width)
1478 {
1479   GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_width, natural_width);
1480 }
1481
1482
1483
1484 /**
1485  * gtk_expander_new:
1486  * @label: the text of the label
1487  * 
1488  * Creates a new expander using @label as the text of the label.
1489  * 
1490  * Return value: a new #GtkExpander widget.
1491  *
1492  * Since: 2.4
1493  **/
1494 GtkWidget *
1495 gtk_expander_new (const gchar *label)
1496 {
1497   return g_object_new (GTK_TYPE_EXPANDER, "label", label, NULL);
1498 }
1499
1500 /**
1501  * gtk_expander_new_with_mnemonic:
1502  * @label: (allow-none): the text of the label with an underscore in front of the
1503  *         mnemonic character
1504  *
1505  * Creates a new expander using @label as the text of the label.
1506  * If characters in @label are preceded by an underscore, they are underlined.
1507  * If you need a literal underscore character in a label, use '__' (two 
1508  * underscores). The first underlined character represents a keyboard 
1509  * accelerator called a mnemonic.
1510  * Pressing Alt and that key activates the button.
1511  * 
1512  * Return value: a new #GtkExpander widget.
1513  *
1514  * Since: 2.4
1515  **/
1516 GtkWidget *
1517 gtk_expander_new_with_mnemonic (const gchar *label)
1518 {
1519   return g_object_new (GTK_TYPE_EXPANDER,
1520                        "label", label,
1521                        "use-underline", TRUE,
1522                        NULL);
1523 }
1524
1525 static gboolean
1526 gtk_expander_animation_timeout (GtkExpander *expander)
1527 {
1528   GtkExpanderPrivate *priv = expander->priv;
1529   GtkWidget *widget = GTK_WIDGET (expander);
1530   GtkWidget *child;
1531   GdkRectangle area;
1532   gboolean finish = FALSE;
1533
1534   if (gtk_widget_get_realized (widget))
1535     {
1536       get_expander_bounds (expander, &area);
1537       gdk_window_invalidate_rect (gtk_widget_get_window (widget), &area, TRUE);
1538     }
1539
1540   if (priv->expanded)
1541     {
1542       if (priv->expander_style == GTK_EXPANDER_COLLAPSED)
1543         {
1544           priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED;
1545         }
1546       else
1547         {
1548           priv->expander_style = GTK_EXPANDER_EXPANDED;
1549           finish = TRUE;
1550         }
1551     }
1552   else
1553     {
1554       if (priv->expander_style == GTK_EXPANDER_EXPANDED)
1555         {
1556           priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED;
1557         }
1558       else
1559         {
1560           priv->expander_style = GTK_EXPANDER_COLLAPSED;
1561           finish = TRUE;
1562         }
1563     }
1564
1565   if (finish)
1566     {
1567       priv->animation_timeout = 0;
1568
1569       child = gtk_bin_get_child (GTK_BIN (expander));
1570       if (child)
1571         gtk_widget_set_child_visible (child, priv->expanded);
1572       gtk_widget_queue_resize (widget);
1573     }
1574
1575   return !finish;
1576 }
1577
1578 static void
1579 gtk_expander_start_animation (GtkExpander *expander)
1580 {
1581   GtkExpanderPrivate *priv = expander->priv;
1582
1583   if (priv->animation_timeout)
1584     g_source_remove (priv->animation_timeout);
1585
1586   priv->animation_timeout =
1587                 gdk_threads_add_timeout (50,
1588                                (GSourceFunc) gtk_expander_animation_timeout,
1589                                expander);
1590 }
1591
1592 /**
1593  * gtk_expander_set_expanded:
1594  * @expander: a #GtkExpander
1595  * @expanded: whether the child widget is revealed
1596  *
1597  * Sets the state of the expander. Set to %TRUE, if you want
1598  * the child widget to be revealed, and %FALSE if you want the
1599  * child widget to be hidden.
1600  *
1601  * Since: 2.4
1602  **/
1603 void
1604 gtk_expander_set_expanded (GtkExpander *expander,
1605                            gboolean     expanded)
1606 {
1607   GtkExpanderPrivate *priv;
1608   GtkWidget *child;
1609
1610   g_return_if_fail (GTK_IS_EXPANDER (expander));
1611
1612   priv = expander->priv;
1613
1614   expanded = expanded != FALSE;
1615
1616   if (priv->expanded != expanded)
1617     {
1618       GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (expander));
1619       gboolean     enable_animations;
1620
1621       priv->expanded = expanded;
1622
1623       g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
1624
1625       if (enable_animations && gtk_widget_get_realized (GTK_WIDGET (expander)))
1626         {
1627           gtk_expander_start_animation (expander);
1628         }
1629       else
1630         {
1631           priv->expander_style = expanded ? GTK_EXPANDER_EXPANDED :
1632                                             GTK_EXPANDER_COLLAPSED;
1633
1634           child = gtk_bin_get_child (GTK_BIN (expander));
1635           if (child)
1636             {
1637               gtk_widget_set_child_visible (child, priv->expanded);
1638               gtk_widget_queue_resize (GTK_WIDGET (expander));
1639             }
1640         }
1641
1642       g_object_notify (G_OBJECT (expander), "expanded");
1643     }
1644 }
1645
1646 /**
1647  * gtk_expander_get_expanded:
1648  * @expander:a #GtkExpander
1649  *
1650  * Queries a #GtkExpander and returns its current state. Returns %TRUE
1651  * if the child widget is revealed.
1652  *
1653  * See gtk_expander_set_expanded().
1654  *
1655  * Return value: the current state of the expander.
1656  *
1657  * Since: 2.4
1658  **/
1659 gboolean
1660 gtk_expander_get_expanded (GtkExpander *expander)
1661 {
1662   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1663
1664   return expander->priv->expanded;
1665 }
1666
1667 /**
1668  * gtk_expander_set_spacing:
1669  * @expander: a #GtkExpander
1670  * @spacing: distance between the expander and child in pixels.
1671  *
1672  * Sets the spacing field of @expander, which is the number of pixels to
1673  * place between expander and the child.
1674  *
1675  * Since: 2.4
1676  **/
1677 void
1678 gtk_expander_set_spacing (GtkExpander *expander,
1679                           gint         spacing)
1680 {
1681   g_return_if_fail (GTK_IS_EXPANDER (expander));
1682   g_return_if_fail (spacing >= 0);
1683
1684   if (expander->priv->spacing != spacing)
1685     {
1686       expander->priv->spacing = spacing;
1687
1688       gtk_widget_queue_resize (GTK_WIDGET (expander));
1689
1690       g_object_notify (G_OBJECT (expander), "spacing");
1691     }
1692 }
1693
1694 /**
1695  * gtk_expander_get_spacing:
1696  * @expander: a #GtkExpander
1697  *
1698  * Gets the value set by gtk_expander_set_spacing().
1699  *
1700  * Return value: spacing between the expander and child.
1701  *
1702  * Since: 2.4
1703  **/
1704 gint
1705 gtk_expander_get_spacing (GtkExpander *expander)
1706 {
1707   g_return_val_if_fail (GTK_IS_EXPANDER (expander), 0);
1708
1709   return expander->priv->spacing;
1710 }
1711
1712 /**
1713  * gtk_expander_set_label:
1714  * @expander: a #GtkExpander
1715  * @label: (allow-none): a string
1716  *
1717  * Sets the text of the label of the expander to @label.
1718  *
1719  * This will also clear any previously set labels.
1720  *
1721  * Since: 2.4
1722  **/
1723 void
1724 gtk_expander_set_label (GtkExpander *expander,
1725                         const gchar *label)
1726 {
1727   g_return_if_fail (GTK_IS_EXPANDER (expander));
1728
1729   if (!label)
1730     {
1731       gtk_expander_set_label_widget (expander, NULL);
1732     }
1733   else
1734     {
1735       GtkWidget *child;
1736
1737       child = gtk_label_new (label);
1738       gtk_label_set_use_underline (GTK_LABEL (child), expander->priv->use_underline);
1739       gtk_label_set_use_markup (GTK_LABEL (child), expander->priv->use_markup);
1740       gtk_widget_show (child);
1741
1742       gtk_expander_set_label_widget (expander, child);
1743     }
1744
1745   g_object_notify (G_OBJECT (expander), "label");
1746 }
1747
1748 /**
1749  * gtk_expander_get_label:
1750  * @expander: a #GtkExpander
1751  *
1752  * Fetches the text from a label widget including any embedded
1753  * underlines indicating mnemonics and Pango markup, as set by
1754  * gtk_expander_set_label(). If the label text has not been set the
1755  * return value will be %NULL. This will be the case if you create an
1756  * empty button with gtk_button_new() to use as a container.
1757  *
1758  * Note that this function behaved differently in versions prior to
1759  * 2.14 and used to return the label text stripped of embedded
1760  * underlines indicating mnemonics and Pango markup. This problem can
1761  * be avoided by fetching the label text directly from the label
1762  * widget.
1763  *
1764  * Return value: The text of the label widget. This string is owned
1765  * by the widget and must not be modified or freed.
1766  *
1767  * Since: 2.4
1768  **/
1769 G_CONST_RETURN char *
1770 gtk_expander_get_label (GtkExpander *expander)
1771 {
1772   GtkExpanderPrivate *priv;
1773
1774   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1775
1776   priv = expander->priv;
1777
1778   if (GTK_IS_LABEL (priv->label_widget))
1779     return gtk_label_get_label (GTK_LABEL (priv->label_widget));
1780   else
1781     return NULL;
1782 }
1783
1784 /**
1785  * gtk_expander_set_use_underline:
1786  * @expander: a #GtkExpander
1787  * @use_underline: %TRUE if underlines in the text indicate mnemonics
1788  *
1789  * If true, an underline in the text of the expander label indicates
1790  * the next character should be used for the mnemonic accelerator key.
1791  *
1792  * Since: 2.4
1793  **/
1794 void
1795 gtk_expander_set_use_underline (GtkExpander *expander,
1796                                 gboolean     use_underline)
1797 {
1798   GtkExpanderPrivate *priv;
1799
1800   g_return_if_fail (GTK_IS_EXPANDER (expander));
1801
1802   priv = expander->priv;
1803
1804   use_underline = use_underline != FALSE;
1805
1806   if (priv->use_underline != use_underline)
1807     {
1808       priv->use_underline = use_underline;
1809
1810       if (GTK_IS_LABEL (priv->label_widget))
1811         gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), use_underline);
1812
1813       g_object_notify (G_OBJECT (expander), "use-underline");
1814     }
1815 }
1816
1817 /**
1818  * gtk_expander_get_use_underline:
1819  * @expander: a #GtkExpander
1820  *
1821  * Returns whether an embedded underline in the expander label indicates a
1822  * mnemonic. See gtk_expander_set_use_underline().
1823  *
1824  * Return value: %TRUE if an embedded underline in the expander label
1825  *               indicates the mnemonic accelerator keys.
1826  *
1827  * Since: 2.4
1828  **/
1829 gboolean
1830 gtk_expander_get_use_underline (GtkExpander *expander)
1831 {
1832   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1833
1834   return expander->priv->use_underline;
1835 }
1836
1837 /**
1838  * gtk_expander_set_use_markup:
1839  * @expander: a #GtkExpander
1840  * @use_markup: %TRUE if the label's text should be parsed for markup
1841  *
1842  * Sets whether the text of the label contains markup in <link
1843  * linkend="PangoMarkupFormat">Pango's text markup
1844  * language</link>. See gtk_label_set_markup().
1845  *
1846  * Since: 2.4
1847  **/
1848 void
1849 gtk_expander_set_use_markup (GtkExpander *expander,
1850                              gboolean     use_markup)
1851 {
1852   GtkExpanderPrivate *priv;
1853
1854   g_return_if_fail (GTK_IS_EXPANDER (expander));
1855
1856   priv = expander->priv;
1857
1858   use_markup = use_markup != FALSE;
1859
1860   if (priv->use_markup != use_markup)
1861     {
1862       priv->use_markup = use_markup;
1863
1864       if (GTK_IS_LABEL (priv->label_widget))
1865         gtk_label_set_use_markup (GTK_LABEL (priv->label_widget), use_markup);
1866
1867       g_object_notify (G_OBJECT (expander), "use-markup");
1868     }
1869 }
1870
1871 /**
1872  * gtk_expander_get_use_markup:
1873  * @expander: a #GtkExpander
1874  *
1875  * Returns whether the label's text is interpreted as marked up with
1876  * the <link linkend="PangoMarkupFormat">Pango text markup
1877  * language</link>. See gtk_expander_set_use_markup ().
1878  *
1879  * Return value: %TRUE if the label's text will be parsed for markup
1880  *
1881  * Since: 2.4
1882  **/
1883 gboolean
1884 gtk_expander_get_use_markup (GtkExpander *expander)
1885 {
1886   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1887
1888   return expander->priv->use_markup;
1889 }
1890
1891 /**
1892  * gtk_expander_set_label_widget:
1893  * @expander: a #GtkExpander
1894  * @label_widget: (allow-none): the new label widget
1895  *
1896  * Set the label widget for the expander. This is the widget
1897  * that will appear embedded alongside the expander arrow.
1898  *
1899  * Since: 2.4
1900  **/
1901 void
1902 gtk_expander_set_label_widget (GtkExpander *expander,
1903                                GtkWidget   *label_widget)
1904 {
1905   GtkExpanderPrivate *priv;
1906   GtkWidget          *widget;
1907
1908   g_return_if_fail (GTK_IS_EXPANDER (expander));
1909   g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
1910   g_return_if_fail (label_widget == NULL || gtk_widget_get_parent (label_widget) == NULL);
1911
1912   priv = expander->priv;
1913
1914   if (priv->label_widget == label_widget)
1915     return;
1916
1917   if (priv->label_widget)
1918     {
1919       gtk_widget_set_state_flags (priv->label_widget, 0, TRUE);
1920       gtk_widget_unparent (priv->label_widget);
1921     }
1922
1923   priv->label_widget = label_widget;
1924   widget = GTK_WIDGET (expander);
1925
1926   if (label_widget)
1927     {
1928       priv->label_widget = label_widget;
1929
1930       gtk_widget_set_parent (label_widget, widget);
1931
1932       if (priv->prelight)
1933         gtk_widget_set_state_flags (label_widget,
1934                                     GTK_STATE_FLAG_PRELIGHT,
1935                                     FALSE);
1936     }
1937
1938   if (gtk_widget_get_visible (widget))
1939     gtk_widget_queue_resize (widget);
1940
1941   g_object_freeze_notify (G_OBJECT (expander));
1942   g_object_notify (G_OBJECT (expander), "label-widget");
1943   g_object_notify (G_OBJECT (expander), "label");
1944   g_object_thaw_notify (G_OBJECT (expander));
1945 }
1946
1947 /**
1948  * gtk_expander_get_label_widget:
1949  * @expander: a #GtkExpander
1950  *
1951  * Retrieves the label widget for the frame. See
1952  * gtk_expander_set_label_widget().
1953  *
1954  * Return value: (transfer none): the label widget,
1955  *     or %NULL if there is none.
1956  *
1957  * Since: 2.4
1958  **/
1959 GtkWidget *
1960 gtk_expander_get_label_widget (GtkExpander *expander)
1961 {
1962   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1963
1964   return expander->priv->label_widget;
1965 }
1966
1967 /**
1968  * gtk_expander_set_label_fill:
1969  * @expander: a #GtkExpander
1970  * @label_fill: %TRUE if the label should should fill all available horizontal
1971  *              space
1972  *
1973  * Sets whether the label widget should fill all available horizontal space
1974  * allocated to @expander.
1975  *
1976  * Since: 2.22
1977  */
1978 void
1979 gtk_expander_set_label_fill (GtkExpander *expander,
1980                              gboolean     label_fill)
1981 {
1982   GtkExpanderPrivate *priv;
1983
1984   g_return_if_fail (GTK_IS_EXPANDER (expander));
1985
1986   priv = expander->priv;
1987
1988   label_fill = label_fill != FALSE;
1989
1990   if (priv->label_fill != label_fill)
1991     {
1992       priv->label_fill = label_fill;
1993
1994       if (priv->label_widget != NULL)
1995         gtk_widget_queue_resize (GTK_WIDGET (expander));
1996
1997       g_object_notify (G_OBJECT (expander), "label-fill");
1998     }
1999 }
2000
2001 /**
2002  * gtk_expander_get_label_fill:
2003  * @expander: a #GtkExpander
2004  *
2005  * Returns whether the label widget will fill all available horizontal
2006  * space allocated to @expander.
2007  *
2008  * Return value: %TRUE if the label widget will fill all available horizontal
2009  *               space
2010  *
2011  * Since: 2.22
2012  */
2013 gboolean
2014 gtk_expander_get_label_fill (GtkExpander *expander)
2015 {
2016   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
2017
2018   return expander->priv->label_fill;
2019 }
2020