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