]> Pileus Git - ~andy/gtk/blob - gtk/gtkexpander.c
Remove GtkObject completely
[~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   GtkWidget *event_widget;
974
975   event_widget = gtk_get_event_widget ((GdkEvent *) event);
976
977   if (event_widget == widget &&
978       event->detail != GDK_NOTIFY_INFERIOR)
979     {
980       expander->priv->prelight = TRUE;
981
982       if (expander->priv->label_widget)
983         gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_PRELIGHT);
984
985       gtk_expander_redraw_expander (expander);
986     }
987
988   return FALSE;
989 }
990
991 static gboolean
992 gtk_expander_leave_notify (GtkWidget        *widget,
993                            GdkEventCrossing *event)
994 {
995   GtkExpander *expander = GTK_EXPANDER (widget);
996   GtkWidget *event_widget;
997
998   event_widget = gtk_get_event_widget ((GdkEvent *) event);
999
1000   if (event_widget == widget &&
1001       event->detail != GDK_NOTIFY_INFERIOR)
1002     {
1003       expander->priv->prelight = FALSE;
1004
1005       if (expander->priv->label_widget)
1006         gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_NORMAL);
1007
1008       gtk_expander_redraw_expander (expander);
1009     }
1010
1011   return FALSE;
1012 }
1013
1014 static gboolean
1015 expand_timeout (gpointer data)
1016 {
1017   GtkExpander *expander = GTK_EXPANDER (data);
1018   GtkExpanderPrivate *priv = expander->priv;
1019
1020   priv->expand_timer = 0;
1021   gtk_expander_set_expanded (expander, TRUE);
1022
1023   return FALSE;
1024 }
1025
1026 static gboolean
1027 gtk_expander_drag_motion (GtkWidget        *widget,
1028                           GdkDragContext   *context,
1029                           gint              x,
1030                           gint              y,
1031                           guint             time)
1032 {
1033   GtkExpander *expander = GTK_EXPANDER (widget);
1034   GtkExpanderPrivate *priv = expander->priv;
1035
1036   if (!priv->expanded && !priv->expand_timer)
1037     {
1038       GtkSettings *settings;
1039       guint timeout;
1040
1041       settings = gtk_widget_get_settings (widget);
1042       g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
1043
1044       priv->expand_timer = gdk_threads_add_timeout (timeout, (GSourceFunc) expand_timeout, expander);
1045     }
1046
1047   return TRUE;
1048 }
1049
1050 static void
1051 gtk_expander_drag_leave (GtkWidget      *widget,
1052                          GdkDragContext *context,
1053                          guint           time)
1054 {
1055   GtkExpander *expander = GTK_EXPANDER (widget);
1056   GtkExpanderPrivate *priv = expander->priv;
1057
1058   if (priv->expand_timer)
1059     {
1060       g_source_remove (priv->expand_timer);
1061       priv->expand_timer = 0;
1062     }
1063 }
1064
1065 typedef enum
1066 {
1067   FOCUS_NONE,
1068   FOCUS_WIDGET,
1069   FOCUS_LABEL,
1070   FOCUS_CHILD
1071 } FocusSite;
1072
1073 static gboolean
1074 focus_current_site (GtkExpander      *expander,
1075                     GtkDirectionType  direction)
1076 {
1077   GtkWidget *current_focus;
1078
1079   current_focus = gtk_container_get_focus_child (GTK_CONTAINER (expander));
1080
1081   if (!current_focus)
1082     return FALSE;
1083
1084   return gtk_widget_child_focus (current_focus, direction);
1085 }
1086
1087 static gboolean
1088 focus_in_site (GtkExpander      *expander,
1089                FocusSite         site,
1090                GtkDirectionType  direction)
1091 {
1092   switch (site)
1093     {
1094     case FOCUS_WIDGET:
1095       gtk_widget_grab_focus (GTK_WIDGET (expander));
1096       return TRUE;
1097     case FOCUS_LABEL:
1098       if (expander->priv->label_widget)
1099         return gtk_widget_child_focus (expander->priv->label_widget, direction);
1100       else
1101         return FALSE;
1102     case FOCUS_CHILD:
1103       {
1104         GtkWidget *child = gtk_bin_get_child (GTK_BIN (expander));
1105
1106         if (child && gtk_widget_get_child_visible (child))
1107           return gtk_widget_child_focus (child, direction);
1108         else
1109           return FALSE;
1110       }
1111     case FOCUS_NONE:
1112       break;
1113     }
1114
1115   g_assert_not_reached ();
1116   return FALSE;
1117 }
1118
1119 static FocusSite
1120 get_next_site (GtkExpander      *expander,
1121                FocusSite         site,
1122                GtkDirectionType  direction)
1123 {
1124   gboolean ltr;
1125
1126   ltr = gtk_widget_get_direction (GTK_WIDGET (expander)) != GTK_TEXT_DIR_RTL;
1127
1128   switch (site)
1129     {
1130     case FOCUS_NONE:
1131       switch (direction)
1132         {
1133         case GTK_DIR_TAB_BACKWARD:
1134         case GTK_DIR_LEFT:
1135         case GTK_DIR_UP:
1136           return FOCUS_CHILD;
1137         case GTK_DIR_TAB_FORWARD:
1138         case GTK_DIR_DOWN:
1139         case GTK_DIR_RIGHT:
1140           return FOCUS_WIDGET;
1141         }
1142     case FOCUS_WIDGET:
1143       switch (direction)
1144         {
1145         case GTK_DIR_TAB_BACKWARD:
1146         case GTK_DIR_UP:
1147           return FOCUS_NONE;
1148         case GTK_DIR_LEFT:
1149           return ltr ? FOCUS_NONE : FOCUS_LABEL;
1150         case GTK_DIR_TAB_FORWARD:
1151         case GTK_DIR_DOWN:
1152           return FOCUS_LABEL;
1153         case GTK_DIR_RIGHT:
1154           return ltr ? FOCUS_LABEL : FOCUS_NONE;
1155           break;
1156         }
1157     case FOCUS_LABEL:
1158       switch (direction)
1159         {
1160         case GTK_DIR_TAB_BACKWARD:
1161         case GTK_DIR_UP:
1162           return FOCUS_WIDGET;
1163         case GTK_DIR_LEFT:
1164           return ltr ? FOCUS_WIDGET : FOCUS_CHILD;
1165         case GTK_DIR_TAB_FORWARD:
1166         case GTK_DIR_DOWN:
1167           return FOCUS_CHILD;
1168         case GTK_DIR_RIGHT:
1169           return ltr ? FOCUS_CHILD : FOCUS_WIDGET;
1170           break;
1171         }
1172     case FOCUS_CHILD:
1173       switch (direction)
1174         {
1175         case GTK_DIR_TAB_BACKWARD:
1176         case GTK_DIR_LEFT:
1177         case GTK_DIR_UP:
1178           return FOCUS_LABEL;
1179         case GTK_DIR_TAB_FORWARD:
1180         case GTK_DIR_DOWN:
1181         case GTK_DIR_RIGHT:
1182           return FOCUS_NONE;
1183         }
1184     }
1185
1186   g_assert_not_reached ();
1187   return FOCUS_NONE;
1188 }
1189
1190 static gboolean
1191 gtk_expander_focus (GtkWidget        *widget,
1192                     GtkDirectionType  direction)
1193 {
1194   GtkExpander *expander = GTK_EXPANDER (widget);
1195   
1196   if (!focus_current_site (expander, direction))
1197     {
1198       GtkWidget *old_focus_child;
1199       gboolean widget_is_focus;
1200       FocusSite site = FOCUS_NONE;
1201       
1202       widget_is_focus = gtk_widget_is_focus (widget);
1203       old_focus_child = gtk_container_get_focus_child (GTK_CONTAINER (widget));
1204       
1205       if (old_focus_child && old_focus_child == expander->priv->label_widget)
1206         site = FOCUS_LABEL;
1207       else if (old_focus_child)
1208         site = FOCUS_CHILD;
1209       else if (widget_is_focus)
1210         site = FOCUS_WIDGET;
1211
1212       while ((site = get_next_site (expander, site, direction)) != FOCUS_NONE)
1213         {
1214           if (focus_in_site (expander, site, direction))
1215             return TRUE;
1216         }
1217
1218       return FALSE;
1219     }
1220
1221   return TRUE;
1222 }
1223
1224 static void
1225 gtk_expander_add (GtkContainer *container,
1226                   GtkWidget    *widget)
1227 {
1228   GTK_CONTAINER_CLASS (gtk_expander_parent_class)->add (container, widget);
1229
1230   gtk_widget_set_child_visible (widget, GTK_EXPANDER (container)->priv->expanded);
1231   gtk_widget_queue_resize (GTK_WIDGET (container));
1232 }
1233
1234 static void
1235 gtk_expander_remove (GtkContainer *container,
1236                      GtkWidget    *widget)
1237 {
1238   GtkExpander *expander = GTK_EXPANDER (container);
1239
1240   if (GTK_EXPANDER (expander)->priv->label_widget == widget)
1241     gtk_expander_set_label_widget (expander, NULL);
1242   else
1243     GTK_CONTAINER_CLASS (gtk_expander_parent_class)->remove (container, widget);
1244 }
1245
1246 static void
1247 gtk_expander_forall (GtkContainer *container,
1248                      gboolean      include_internals,
1249                      GtkCallback   callback,
1250                      gpointer      callback_data)
1251 {
1252   GtkBin *bin = GTK_BIN (container);
1253   GtkExpanderPrivate *priv = GTK_EXPANDER (container)->priv;
1254   GtkWidget *child;
1255
1256   child = gtk_bin_get_child (bin);
1257   if (child)
1258     (* callback) (child, callback_data);
1259
1260   if (priv->label_widget)
1261     (* callback) (priv->label_widget, callback_data);
1262 }
1263
1264 static void
1265 gtk_expander_activate (GtkExpander *expander)
1266 {
1267   gtk_expander_set_expanded (expander, !expander->priv->expanded);
1268 }
1269
1270
1271 static void     
1272 gtk_expander_get_preferred_width (GtkWidget *widget,
1273                                   gint      *minimum_size,
1274                                   gint      *natural_size)
1275 {
1276   GtkExpander *expander;
1277   GtkWidget *child;
1278   GtkExpanderPrivate *priv;
1279   gint border_width;
1280   gint expander_size;
1281   gint expander_spacing;
1282   gboolean interior_focus;
1283   gint focus_width;
1284   gint focus_pad;
1285
1286   child = gtk_bin_get_child (GTK_BIN (widget));
1287   expander = GTK_EXPANDER (widget);
1288   priv = expander->priv;
1289
1290   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1291
1292   gtk_widget_style_get (GTK_WIDGET (widget),
1293                         "interior-focus", &interior_focus,
1294                         "focus-line-width", &focus_width,
1295                         "focus-padding", &focus_pad,
1296                         "expander-size", &expander_size,
1297                         "expander-spacing", &expander_spacing,
1298                         NULL);
1299
1300   *minimum_size = *natural_size = 
1301     expander_size + 2 * expander_spacing +
1302     2 * focus_width + 2 * focus_pad;
1303
1304   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1305     {
1306       gint label_min, label_nat;
1307
1308       gtk_widget_get_preferred_width (priv->label_widget, 
1309                                       &label_min, &label_nat);
1310
1311       *minimum_size += label_min;
1312       *natural_size += label_nat;
1313     }
1314
1315   if (child && gtk_widget_get_child_visible (child))
1316     {
1317       gint child_min, child_nat;
1318
1319       gtk_widget_get_preferred_width (child, 
1320                                       &child_min, &child_nat);
1321
1322       *minimum_size = MAX (*minimum_size, child_min);
1323       *natural_size = MAX (*natural_size, child_nat);
1324
1325     }
1326
1327   *minimum_size += 2 * border_width;
1328   *natural_size += 2 * border_width;
1329 }
1330
1331 static void
1332 gtk_expander_get_preferred_height (GtkWidget *widget,
1333                                    gint      *minimum_size,
1334                                    gint      *natural_size)
1335 {  
1336   GtkExpander *expander;
1337   GtkWidget *child;
1338   GtkExpanderPrivate *priv;
1339   gint border_width;
1340   gint expander_size;
1341   gint expander_spacing;
1342   gboolean interior_focus;
1343   gint focus_width;
1344   gint focus_pad;
1345
1346   child = gtk_bin_get_child (GTK_BIN (widget));
1347   expander = GTK_EXPANDER (widget);
1348   priv = expander->priv;
1349
1350   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1351
1352   gtk_widget_style_get (GTK_WIDGET (widget),
1353                         "interior-focus", &interior_focus,
1354                         "focus-line-width", &focus_width,
1355                         "focus-padding", &focus_pad,
1356                         "expander-size", &expander_size,
1357                         "expander-spacing", &expander_spacing,
1358                         NULL);
1359
1360   *minimum_size = *natural_size = 
1361     interior_focus ? (2 * focus_width + 2 * focus_pad) : 0;
1362
1363
1364   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1365     {
1366       gint label_min, label_nat;
1367
1368       gtk_widget_get_preferred_height (priv->label_widget, 
1369                                        &label_min, &label_nat);
1370       
1371       *minimum_size += label_min;
1372       *natural_size += label_nat;
1373     }
1374
1375   *minimum_size = MAX (*minimum_size, expander_size + 2 * expander_spacing);
1376   *natural_size = MAX (*natural_size, *minimum_size);
1377
1378   if (!interior_focus)
1379     {
1380       gint extra = 2 * focus_width + 2 * focus_pad;
1381       *minimum_size += extra;
1382       *natural_size += extra;
1383     }
1384
1385   if (child && gtk_widget_get_child_visible (child))
1386     {
1387       gint child_min, child_nat;
1388
1389       gtk_widget_get_preferred_height (child, 
1390                                        &child_min, &child_nat);
1391
1392       *minimum_size += child_min + priv->spacing;
1393       *natural_size += child_nat + priv->spacing;
1394
1395     }
1396
1397   *minimum_size += 2 * border_width;
1398   *natural_size += 2 * border_width;
1399 }
1400
1401 static void
1402 gtk_expander_get_preferred_height_for_width (GtkWidget *widget,
1403                                              gint       width,
1404                                              gint      *minimum_height,
1405                                              gint      *natural_height)
1406 {
1407   GtkExpander *expander;
1408   GtkWidget *child;
1409   GtkExpanderPrivate *priv;
1410   gint border_width;
1411   gint expander_size;
1412   gint expander_spacing;
1413   gboolean interior_focus;
1414   gint focus_width;
1415   gint focus_pad;
1416   gint label_xpad;
1417
1418   child = gtk_bin_get_child (GTK_BIN (widget));
1419   expander = GTK_EXPANDER (widget);
1420   priv = expander->priv;
1421
1422   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1423
1424   gtk_widget_style_get (GTK_WIDGET (widget),
1425                         "interior-focus", &interior_focus,
1426                         "focus-line-width", &focus_width,
1427                         "focus-padding", &focus_pad,
1428                         "expander-size", &expander_size,
1429                         "expander-spacing", &expander_spacing,
1430                         NULL);
1431
1432   label_xpad = 2 * border_width + expander_size + 2 * expander_spacing - 2 * focus_width + 2 * focus_pad;
1433
1434   *minimum_height = *natural_height = 
1435     interior_focus ? (2 * focus_width + 2 * focus_pad) : 0;
1436
1437
1438   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1439     {
1440       gint label_min, label_nat;
1441
1442       gtk_widget_get_preferred_height_for_width (priv->label_widget, 
1443                                                  MAX (width - label_xpad, 1), 
1444                                                  &label_min, &label_nat);
1445       
1446       *minimum_height += label_min;
1447       *natural_height += label_nat;
1448     }
1449
1450   *minimum_height = MAX (*minimum_height, expander_size + 2 * expander_spacing);
1451   *natural_height = MAX (*natural_height, *minimum_height);
1452
1453   if (!interior_focus)
1454     {
1455       gint extra = 2 * focus_width + 2 * focus_pad;
1456       *minimum_height += extra;
1457       *natural_height += extra;
1458     }
1459
1460   if (child && gtk_widget_get_child_visible (child))
1461     {
1462       gint child_min, child_nat;
1463
1464       gtk_widget_get_preferred_height_for_width (child, 
1465                                                  MAX (width - 2 * border_width, 1), 
1466                                                  &child_min, &child_nat);
1467
1468       *minimum_height += child_min + priv->spacing;
1469       *natural_height += child_nat + priv->spacing;
1470     }
1471
1472   *minimum_height += 2 * border_width;
1473   *natural_height += 2 * border_width;
1474 }
1475
1476 static void
1477 gtk_expander_get_preferred_width_for_height (GtkWidget *widget,
1478                                              gint       height,
1479                                              gint      *minimum_width,
1480                                              gint      *natural_width)
1481 {
1482   GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_width, natural_width);
1483 }
1484
1485
1486
1487 /**
1488  * gtk_expander_new:
1489  * @label: the text of the label
1490  * 
1491  * Creates a new expander using @label as the text of the label.
1492  * 
1493  * Return value: a new #GtkExpander widget.
1494  *
1495  * Since: 2.4
1496  **/
1497 GtkWidget *
1498 gtk_expander_new (const gchar *label)
1499 {
1500   return g_object_new (GTK_TYPE_EXPANDER, "label", label, NULL);
1501 }
1502
1503 /**
1504  * gtk_expander_new_with_mnemonic:
1505  * @label: (allow-none): the text of the label with an underscore in front of the
1506  *         mnemonic character
1507  *
1508  * Creates a new expander using @label as the text of the label.
1509  * If characters in @label are preceded by an underscore, they are underlined.
1510  * If you need a literal underscore character in a label, use '__' (two 
1511  * underscores). The first underlined character represents a keyboard 
1512  * accelerator called a mnemonic.
1513  * Pressing Alt and that key activates the button.
1514  * 
1515  * Return value: a new #GtkExpander widget.
1516  *
1517  * Since: 2.4
1518  **/
1519 GtkWidget *
1520 gtk_expander_new_with_mnemonic (const gchar *label)
1521 {
1522   return g_object_new (GTK_TYPE_EXPANDER,
1523                        "label", label,
1524                        "use-underline", TRUE,
1525                        NULL);
1526 }
1527
1528 static gboolean
1529 gtk_expander_animation_timeout (GtkExpander *expander)
1530 {
1531   GtkExpanderPrivate *priv = expander->priv;
1532   GtkWidget *widget = GTK_WIDGET (expander);
1533   GtkWidget *child;
1534   GdkRectangle area;
1535   gboolean finish = FALSE;
1536
1537   if (gtk_widget_get_realized (widget))
1538     {
1539       get_expander_bounds (expander, &area);
1540       gdk_window_invalidate_rect (gtk_widget_get_window (widget), &area, TRUE);
1541     }
1542
1543   if (priv->expanded)
1544     {
1545       if (priv->expander_style == GTK_EXPANDER_COLLAPSED)
1546         {
1547           priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED;
1548         }
1549       else
1550         {
1551           priv->expander_style = GTK_EXPANDER_EXPANDED;
1552           finish = TRUE;
1553         }
1554     }
1555   else
1556     {
1557       if (priv->expander_style == GTK_EXPANDER_EXPANDED)
1558         {
1559           priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED;
1560         }
1561       else
1562         {
1563           priv->expander_style = GTK_EXPANDER_COLLAPSED;
1564           finish = TRUE;
1565         }
1566     }
1567
1568   if (finish)
1569     {
1570       priv->animation_timeout = 0;
1571
1572       child = gtk_bin_get_child (GTK_BIN (expander));
1573       if (child)
1574         gtk_widget_set_child_visible (child, priv->expanded);
1575       gtk_widget_queue_resize (widget);
1576     }
1577
1578   return !finish;
1579 }
1580
1581 static void
1582 gtk_expander_start_animation (GtkExpander *expander)
1583 {
1584   GtkExpanderPrivate *priv = expander->priv;
1585
1586   if (priv->animation_timeout)
1587     g_source_remove (priv->animation_timeout);
1588
1589   priv->animation_timeout =
1590                 gdk_threads_add_timeout (50,
1591                                (GSourceFunc) gtk_expander_animation_timeout,
1592                                expander);
1593 }
1594
1595 /**
1596  * gtk_expander_set_expanded:
1597  * @expander: a #GtkExpander
1598  * @expanded: whether the child widget is revealed
1599  *
1600  * Sets the state of the expander. Set to %TRUE, if you want
1601  * the child widget to be revealed, and %FALSE if you want the
1602  * child widget to be hidden.
1603  *
1604  * Since: 2.4
1605  **/
1606 void
1607 gtk_expander_set_expanded (GtkExpander *expander,
1608                            gboolean     expanded)
1609 {
1610   GtkExpanderPrivate *priv;
1611   GtkWidget *child;
1612
1613   g_return_if_fail (GTK_IS_EXPANDER (expander));
1614
1615   priv = expander->priv;
1616
1617   expanded = expanded != FALSE;
1618
1619   if (priv->expanded != expanded)
1620     {
1621       GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (expander));
1622       gboolean     enable_animations;
1623
1624       priv->expanded = expanded;
1625
1626       g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
1627
1628       if (enable_animations && gtk_widget_get_realized (GTK_WIDGET (expander)))
1629         {
1630           gtk_expander_start_animation (expander);
1631         }
1632       else
1633         {
1634           priv->expander_style = expanded ? GTK_EXPANDER_EXPANDED :
1635                                             GTK_EXPANDER_COLLAPSED;
1636
1637           child = gtk_bin_get_child (GTK_BIN (expander));
1638           if (child)
1639             {
1640               gtk_widget_set_child_visible (child, priv->expanded);
1641               gtk_widget_queue_resize (GTK_WIDGET (expander));
1642             }
1643         }
1644
1645       g_object_notify (G_OBJECT (expander), "expanded");
1646     }
1647 }
1648
1649 /**
1650  * gtk_expander_get_expanded:
1651  * @expander:a #GtkExpander
1652  *
1653  * Queries a #GtkExpander and returns its current state. Returns %TRUE
1654  * if the child widget is revealed.
1655  *
1656  * See gtk_expander_set_expanded().
1657  *
1658  * Return value: the current state of the expander.
1659  *
1660  * Since: 2.4
1661  **/
1662 gboolean
1663 gtk_expander_get_expanded (GtkExpander *expander)
1664 {
1665   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1666
1667   return expander->priv->expanded;
1668 }
1669
1670 /**
1671  * gtk_expander_set_spacing:
1672  * @expander: a #GtkExpander
1673  * @spacing: distance between the expander and child in pixels.
1674  *
1675  * Sets the spacing field of @expander, which is the number of pixels to
1676  * place between expander and the child.
1677  *
1678  * Since: 2.4
1679  **/
1680 void
1681 gtk_expander_set_spacing (GtkExpander *expander,
1682                           gint         spacing)
1683 {
1684   g_return_if_fail (GTK_IS_EXPANDER (expander));
1685   g_return_if_fail (spacing >= 0);
1686
1687   if (expander->priv->spacing != spacing)
1688     {
1689       expander->priv->spacing = spacing;
1690
1691       gtk_widget_queue_resize (GTK_WIDGET (expander));
1692
1693       g_object_notify (G_OBJECT (expander), "spacing");
1694     }
1695 }
1696
1697 /**
1698  * gtk_expander_get_spacing:
1699  * @expander: a #GtkExpander
1700  *
1701  * Gets the value set by gtk_expander_set_spacing().
1702  *
1703  * Return value: spacing between the expander and child.
1704  *
1705  * Since: 2.4
1706  **/
1707 gint
1708 gtk_expander_get_spacing (GtkExpander *expander)
1709 {
1710   g_return_val_if_fail (GTK_IS_EXPANDER (expander), 0);
1711
1712   return expander->priv->spacing;
1713 }
1714
1715 /**
1716  * gtk_expander_set_label:
1717  * @expander: a #GtkExpander
1718  * @label: (allow-none): a string
1719  *
1720  * Sets the text of the label of the expander to @label.
1721  *
1722  * This will also clear any previously set labels.
1723  *
1724  * Since: 2.4
1725  **/
1726 void
1727 gtk_expander_set_label (GtkExpander *expander,
1728                         const gchar *label)
1729 {
1730   g_return_if_fail (GTK_IS_EXPANDER (expander));
1731
1732   if (!label)
1733     {
1734       gtk_expander_set_label_widget (expander, NULL);
1735     }
1736   else
1737     {
1738       GtkWidget *child;
1739
1740       child = gtk_label_new (label);
1741       gtk_label_set_use_underline (GTK_LABEL (child), expander->priv->use_underline);
1742       gtk_label_set_use_markup (GTK_LABEL (child), expander->priv->use_markup);
1743       gtk_widget_show (child);
1744
1745       gtk_expander_set_label_widget (expander, child);
1746     }
1747
1748   g_object_notify (G_OBJECT (expander), "label");
1749 }
1750
1751 /**
1752  * gtk_expander_get_label:
1753  * @expander: a #GtkExpander
1754  *
1755  * Fetches the text from a label widget including any embedded
1756  * underlines indicating mnemonics and Pango markup, as set by
1757  * gtk_expander_set_label(). If the label text has not been set the
1758  * return value will be %NULL. This will be the case if you create an
1759  * empty button with gtk_button_new() to use as a container.
1760  *
1761  * Note that this function behaved differently in versions prior to
1762  * 2.14 and used to return the label text stripped of embedded
1763  * underlines indicating mnemonics and Pango markup. This problem can
1764  * be avoided by fetching the label text directly from the label
1765  * widget.
1766  *
1767  * Return value: The text of the label widget. This string is owned
1768  * by the widget and must not be modified or freed.
1769  *
1770  * Since: 2.4
1771  **/
1772 G_CONST_RETURN char *
1773 gtk_expander_get_label (GtkExpander *expander)
1774 {
1775   GtkExpanderPrivate *priv;
1776
1777   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1778
1779   priv = expander->priv;
1780
1781   if (GTK_IS_LABEL (priv->label_widget))
1782     return gtk_label_get_label (GTK_LABEL (priv->label_widget));
1783   else
1784     return NULL;
1785 }
1786
1787 /**
1788  * gtk_expander_set_use_underline:
1789  * @expander: a #GtkExpander
1790  * @use_underline: %TRUE if underlines in the text indicate mnemonics
1791  *
1792  * If true, an underline in the text of the expander label indicates
1793  * the next character should be used for the mnemonic accelerator key.
1794  *
1795  * Since: 2.4
1796  **/
1797 void
1798 gtk_expander_set_use_underline (GtkExpander *expander,
1799                                 gboolean     use_underline)
1800 {
1801   GtkExpanderPrivate *priv;
1802
1803   g_return_if_fail (GTK_IS_EXPANDER (expander));
1804
1805   priv = expander->priv;
1806
1807   use_underline = use_underline != FALSE;
1808
1809   if (priv->use_underline != use_underline)
1810     {
1811       priv->use_underline = use_underline;
1812
1813       if (GTK_IS_LABEL (priv->label_widget))
1814         gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), use_underline);
1815
1816       g_object_notify (G_OBJECT (expander), "use-underline");
1817     }
1818 }
1819
1820 /**
1821  * gtk_expander_get_use_underline:
1822  * @expander: a #GtkExpander
1823  *
1824  * Returns whether an embedded underline in the expander label indicates a
1825  * mnemonic. See gtk_expander_set_use_underline().
1826  *
1827  * Return value: %TRUE if an embedded underline in the expander label
1828  *               indicates the mnemonic accelerator keys.
1829  *
1830  * Since: 2.4
1831  **/
1832 gboolean
1833 gtk_expander_get_use_underline (GtkExpander *expander)
1834 {
1835   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1836
1837   return expander->priv->use_underline;
1838 }
1839
1840 /**
1841  * gtk_expander_set_use_markup:
1842  * @expander: a #GtkExpander
1843  * @use_markup: %TRUE if the label's text should be parsed for markup
1844  *
1845  * Sets whether the text of the label contains markup in <link
1846  * linkend="PangoMarkupFormat">Pango's text markup
1847  * language</link>. See gtk_label_set_markup().
1848  *
1849  * Since: 2.4
1850  **/
1851 void
1852 gtk_expander_set_use_markup (GtkExpander *expander,
1853                              gboolean     use_markup)
1854 {
1855   GtkExpanderPrivate *priv;
1856
1857   g_return_if_fail (GTK_IS_EXPANDER (expander));
1858
1859   priv = expander->priv;
1860
1861   use_markup = use_markup != FALSE;
1862
1863   if (priv->use_markup != use_markup)
1864     {
1865       priv->use_markup = use_markup;
1866
1867       if (GTK_IS_LABEL (priv->label_widget))
1868         gtk_label_set_use_markup (GTK_LABEL (priv->label_widget), use_markup);
1869
1870       g_object_notify (G_OBJECT (expander), "use-markup");
1871     }
1872 }
1873
1874 /**
1875  * gtk_expander_get_use_markup:
1876  * @expander: a #GtkExpander
1877  *
1878  * Returns whether the label's text is interpreted as marked up with
1879  * the <link linkend="PangoMarkupFormat">Pango text markup
1880  * language</link>. See gtk_expander_set_use_markup ().
1881  *
1882  * Return value: %TRUE if the label's text will be parsed for markup
1883  *
1884  * Since: 2.4
1885  **/
1886 gboolean
1887 gtk_expander_get_use_markup (GtkExpander *expander)
1888 {
1889   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1890
1891   return expander->priv->use_markup;
1892 }
1893
1894 /**
1895  * gtk_expander_set_label_widget:
1896  * @expander: a #GtkExpander
1897  * @label_widget: (allow-none): the new label widget
1898  *
1899  * Set the label widget for the expander. This is the widget
1900  * that will appear embedded alongside the expander arrow.
1901  *
1902  * Since: 2.4
1903  **/
1904 void
1905 gtk_expander_set_label_widget (GtkExpander *expander,
1906                                GtkWidget   *label_widget)
1907 {
1908   GtkExpanderPrivate *priv;
1909   GtkWidget          *widget;
1910
1911   g_return_if_fail (GTK_IS_EXPANDER (expander));
1912   g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
1913   g_return_if_fail (label_widget == NULL || gtk_widget_get_parent (label_widget) == NULL);
1914
1915   priv = expander->priv;
1916
1917   if (priv->label_widget == label_widget)
1918     return;
1919
1920   if (priv->label_widget)
1921     {
1922       gtk_widget_set_state (priv->label_widget, GTK_STATE_NORMAL);
1923       gtk_widget_unparent (priv->label_widget);
1924     }
1925
1926   priv->label_widget = label_widget;
1927   widget = GTK_WIDGET (expander);
1928
1929   if (label_widget)
1930     {
1931       priv->label_widget = label_widget;
1932
1933       gtk_widget_set_parent (label_widget, widget);
1934
1935       if (priv->prelight)
1936         gtk_widget_set_state (label_widget, GTK_STATE_PRELIGHT);
1937     }
1938
1939   if (gtk_widget_get_visible (widget))
1940     gtk_widget_queue_resize (widget);
1941
1942   g_object_freeze_notify (G_OBJECT (expander));
1943   g_object_notify (G_OBJECT (expander), "label-widget");
1944   g_object_notify (G_OBJECT (expander), "label");
1945   g_object_thaw_notify (G_OBJECT (expander));
1946 }
1947
1948 /**
1949  * gtk_expander_get_label_widget:
1950  * @expander: a #GtkExpander
1951  *
1952  * Retrieves the label widget for the frame. See
1953  * gtk_expander_set_label_widget().
1954  *
1955  * Return value: (transfer none): the label widget,
1956  *     or %NULL if there is none.
1957  *
1958  * Since: 2.4
1959  **/
1960 GtkWidget *
1961 gtk_expander_get_label_widget (GtkExpander *expander)
1962 {
1963   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1964
1965   return expander->priv->label_widget;
1966 }
1967
1968 /**
1969  * gtk_expander_set_label_fill:
1970  * @expander: a #GtkExpander
1971  * @label_fill: %TRUE if the label should should fill all available horizontal
1972  *              space
1973  *
1974  * Sets whether the label widget should fill all available horizontal space
1975  * allocated to @expander.
1976  *
1977  * Since: 2.22
1978  */
1979 void
1980 gtk_expander_set_label_fill (GtkExpander *expander,
1981                              gboolean     label_fill)
1982 {
1983   GtkExpanderPrivate *priv;
1984
1985   g_return_if_fail (GTK_IS_EXPANDER (expander));
1986
1987   priv = expander->priv;
1988
1989   label_fill = label_fill != FALSE;
1990
1991   if (priv->label_fill != label_fill)
1992     {
1993       priv->label_fill = label_fill;
1994
1995       if (priv->label_widget != NULL)
1996         gtk_widget_queue_resize (GTK_WIDGET (expander));
1997
1998       g_object_notify (G_OBJECT (expander), "label-fill");
1999     }
2000 }
2001
2002 /**
2003  * gtk_expander_get_label_fill:
2004  * @expander: a #GtkExpander
2005  *
2006  * Returns whether the label widget will fill all available horizontal
2007  * space allocated to @expander.
2008  *
2009  * Return value: %TRUE if the label widget will fill all available horizontal
2010  *               space
2011  *
2012  * Since: 2.22
2013  */
2014 gboolean
2015 gtk_expander_get_label_fill (GtkExpander *expander)
2016 {
2017   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
2018
2019   return expander->priv->label_fill;
2020 }
2021