]> Pileus Git - ~andy/gtk/blob - gtk/gtkexpander.c
bedab00d1e2cab2c9c5000b74b9c7f8376446d90
[~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   GtkAllocation allocation;
434   GtkExpanderPrivate *priv;
435   GdkWindow *window;
436   GdkWindowAttr attributes;
437   gint attributes_mask;
438   gint border_width;
439   GdkRectangle expander_rect;
440   gint label_height;
441
442   priv = GTK_EXPANDER (widget)->priv;
443
444   gtk_widget_set_realized (widget, TRUE);
445
446   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
447
448   get_expander_bounds (GTK_EXPANDER (widget), &expander_rect);
449   
450   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
451     {
452       GtkRequisition label_requisition;
453
454       gtk_widget_get_child_requisition (priv->label_widget, &label_requisition);
455       label_height = label_requisition.height;
456     }
457   else
458     label_height = 0;
459
460   gtk_widget_get_allocation (widget, &allocation);
461
462   attributes.window_type = GDK_WINDOW_CHILD;
463   attributes.x = allocation.x + border_width;
464   attributes.y = allocation.y + border_width;
465   attributes.width = MAX (allocation.width - 2 * border_width, 1);
466   attributes.height = MAX (expander_rect.height, label_height - 2 * border_width);
467   attributes.wclass = GDK_INPUT_ONLY;
468   attributes.event_mask = gtk_widget_get_events (widget)     |
469                                 GDK_BUTTON_PRESS_MASK        |
470                                 GDK_BUTTON_RELEASE_MASK      |
471                                 GDK_ENTER_NOTIFY_MASK        |
472                                 GDK_LEAVE_NOTIFY_MASK;
473
474   attributes_mask = GDK_WA_X | GDK_WA_Y;
475
476   window = gtk_widget_get_parent_window (widget);
477   gtk_widget_set_window (widget, window);
478   g_object_ref (window);
479
480   priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
481                                        &attributes, attributes_mask);
482   gdk_window_set_user_data (priv->event_window, widget);
483
484   gtk_widget_style_attach (widget);
485 }
486
487 static void
488 gtk_expander_unrealize (GtkWidget *widget)
489 {
490   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
491
492   if (priv->event_window)
493     {
494       gdk_window_set_user_data (priv->event_window, NULL);
495       gdk_window_destroy (priv->event_window);
496       priv->event_window = NULL;
497     }
498
499   GTK_WIDGET_CLASS (gtk_expander_parent_class)->unrealize (widget);
500 }
501
502 static void
503 get_expander_bounds (GtkExpander  *expander,
504                      GdkRectangle *rect)
505 {
506   GtkAllocation allocation;
507   GtkWidget *widget;
508   GtkExpanderPrivate *priv;
509   gint border_width;
510   gint expander_size;
511   gint expander_spacing;
512   gboolean interior_focus;
513   gint focus_width;
514   gint focus_pad;
515   gboolean ltr;
516
517   widget = GTK_WIDGET (expander);
518   priv = expander->priv;
519
520   gtk_widget_get_allocation (widget, &allocation);
521
522   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
523
524   gtk_widget_style_get (widget,
525                         "interior-focus", &interior_focus,
526                         "focus-line-width", &focus_width,
527                         "focus-padding", &focus_pad,
528                         "expander-size", &expander_size,
529                         "expander-spacing", &expander_spacing,
530                         NULL);
531
532   ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
533
534   rect->x = allocation.x + border_width;
535   rect->y = allocation.y + border_width;
536
537   if (ltr)
538     rect->x += expander_spacing;
539   else
540     rect->x += allocation.width - 2 * border_width -
541                expander_spacing - expander_size;
542
543   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
544     {
545       GtkAllocation label_allocation;
546
547       gtk_widget_get_allocation (priv->label_widget, &label_allocation);
548
549       if (expander_size < label_allocation.height)
550         rect->y += focus_width + focus_pad + (label_allocation.height - expander_size) / 2;
551       else
552         rect->y += expander_spacing;
553     }
554   else
555     {
556       rect->y += expander_spacing;
557     }
558
559   if (!interior_focus)
560     {
561       if (ltr)
562         rect->x += focus_width + focus_pad;
563       else
564         rect->x -= focus_width + focus_pad;
565       rect->y += focus_width + focus_pad;
566     }
567
568   rect->width = rect->height = expander_size;
569 }
570
571 static void
572 gtk_expander_size_allocate (GtkWidget     *widget,
573                             GtkAllocation *allocation)
574 {
575   GtkExpander *expander;
576   GtkWidget *child;
577   GtkExpanderPrivate *priv;
578   gboolean child_visible = FALSE;
579   guint border_width;
580   gint expander_size;
581   gint expander_spacing;
582   gboolean interior_focus;
583   gint focus_width;
584   gint focus_pad;
585   gint label_height, top_min_height;
586   gint label_xpad, label_xoffset;
587   gint child_ypad, child_yoffset;
588
589   expander = GTK_EXPANDER (widget);
590   child    = gtk_bin_get_child (GTK_BIN (widget));
591   priv     = expander->priv;
592
593   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
594
595   gtk_widget_set_allocation (widget, allocation);
596
597   gtk_widget_style_get (widget,
598                         "interior-focus", &interior_focus,
599                         "focus-line-width", &focus_width,
600                         "focus-padding", &focus_pad,
601                         "expander-size", &expander_size,
602                         "expander-spacing", &expander_spacing,
603                         NULL);
604
605
606   /* Calculate some offsets/padding first */
607   label_xoffset = border_width + expander_size + focus_width + 2 * expander_spacing + focus_pad;
608   label_xpad = 2 * border_width + expander_size + 2 * focus_width + 2 * expander_spacing + 2 * focus_pad;
609
610   child_yoffset  = border_width + priv->spacing + (interior_focus ? 0 : 2 * focus_width + 2 * focus_pad);
611   child_ypad     = 2 * border_width + priv->spacing + (interior_focus ? 0 : 2 * focus_width + 2 * focus_pad);
612   top_min_height = 2 * expander_spacing + expander_size;
613
614   child_visible = (child && GTK_WIDGET_CHILD_VISIBLE (child));
615
616   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
617     {
618       GtkAllocation label_allocation;
619       gint          natural_label_width;
620       gboolean ltr;
621
622       gtk_size_request_get_width (GTK_SIZE_REQUEST (priv->label_widget), NULL, &natural_label_width);
623
624       if (priv->label_fill)
625         label_allocation.width = allocation->width - label_xpad;
626       else
627         label_allocation.width = MIN (natural_label_width, allocation->width - label_xpad);
628       label_allocation.width = MAX (label_allocation.width, 1);
629
630       /* We distribute the minimum height to the label widget and prioritize
631        * the child widget giving it the remaining height */
632       gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (priv->label_widget),
633                                              label_allocation.width, &label_height, NULL);
634
635       ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
636
637       if (priv->label_fill)
638         label_allocation.x = allocation->x + label_xoffset;
639       else if (ltr)
640         label_allocation.x = allocation->x + label_xoffset;
641       else
642         label_allocation.x = allocation->x + allocation->width -
643                              (label_allocation.width + label_xoffset);
644
645       label_allocation.y = allocation->y + border_width + focus_width + focus_pad;
646       label_allocation.height = MIN (label_height,
647                                      allocation->height - 2 * border_width -
648                                      2 * focus_width - 2 * focus_pad -
649                                      (child_visible ? priv->spacing : 0));
650       label_allocation.height = MAX (label_allocation.height, 1);
651
652       gtk_widget_size_allocate (priv->label_widget, &label_allocation);
653
654       label_height = label_allocation.height;
655     }
656   else
657     {
658       label_height = 0;
659     }
660
661   if (gtk_widget_get_realized (widget))
662     {
663       GdkRectangle rect;
664
665       get_expander_bounds (expander, &rect);
666
667       gdk_window_move_resize (priv->event_window,
668                               allocation->x + border_width,
669                               allocation->y + border_width,
670                               MAX (allocation->width - 2 * border_width, 1),
671                               MAX (rect.height, label_height - 2 * border_width));
672     }
673
674   if (child_visible)
675     {
676       GtkAllocation child_allocation;
677       gint top_height;
678
679       top_height = MAX (top_min_height,
680                         label_height + (interior_focus ? 2 * focus_width + 2 * focus_pad : 0));
681
682       child_allocation.x = allocation->x + border_width;
683       child_allocation.y = allocation->y + top_height + child_yoffset;
684
685       child_allocation.width = MAX (allocation->width - 2 * border_width, 1);
686       child_allocation.height = allocation->height - top_height - child_ypad;
687       child_allocation.height = MAX (child_allocation.height, 1);
688
689       gtk_widget_size_allocate (child, &child_allocation);
690     }
691 }
692
693 static void
694 gtk_expander_map (GtkWidget *widget)
695 {
696   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
697
698   if (priv->label_widget)
699     gtk_widget_map (priv->label_widget);
700
701   GTK_WIDGET_CLASS (gtk_expander_parent_class)->map (widget);
702
703   if (priv->event_window)
704     gdk_window_show (priv->event_window);
705 }
706
707 static void
708 gtk_expander_unmap (GtkWidget *widget)
709 {
710   GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;
711
712   if (priv->event_window)
713     gdk_window_hide (priv->event_window);
714
715   GTK_WIDGET_CLASS (gtk_expander_parent_class)->unmap (widget);
716
717   if (priv->label_widget)
718     gtk_widget_unmap (priv->label_widget);
719 }
720
721 static void
722 gtk_expander_paint_prelight (GtkExpander *expander)
723 {
724   GtkAllocation allocation;
725   GtkWidget *widget;
726   GtkContainer *container;
727   GtkExpanderPrivate *priv;
728   GdkRectangle area;
729   gboolean interior_focus;
730   int focus_width;
731   int focus_pad;
732   int expander_size;
733   int expander_spacing;
734   guint border_width;
735
736   priv = expander->priv;
737   widget = GTK_WIDGET (expander);
738   container = GTK_CONTAINER (expander);
739
740   gtk_widget_style_get (widget,
741                         "interior-focus", &interior_focus,
742                         "focus-line-width", &focus_width,
743                         "focus-padding", &focus_pad,
744                         "expander-size", &expander_size,
745                         "expander-spacing", &expander_spacing,
746                         NULL);
747
748   gtk_widget_get_allocation (widget, &allocation);
749
750   border_width = gtk_container_get_border_width (container);
751   area.x = allocation.x + border_width;
752   area.y = allocation.y + border_width;
753   area.width = allocation.width - (2 * border_width);
754
755   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
756     {
757       GtkAllocation label_widget_allocation;
758
759       gtk_widget_get_allocation (priv->label_widget, &label_widget_allocation);
760       area.height = label_widget_allocation.height;
761     }
762   else
763     area.height = 0;
764
765   area.height += interior_focus ? (focus_width + focus_pad) * 2 : 0;
766   area.height = MAX (area.height, expander_size + 2 * expander_spacing);
767   area.height += !interior_focus ? (focus_width + focus_pad) * 2 : 0;
768
769   gtk_paint_flat_box (gtk_widget_get_style (widget),
770                       gtk_widget_get_window (widget),
771                       GTK_STATE_PRELIGHT,
772                       GTK_SHADOW_ETCHED_OUT,
773                       &area, widget, "expander",
774                       area.x, area.y,
775                       area.width, area.height);
776 }
777
778 static void
779 gtk_expander_paint (GtkExpander *expander)
780 {
781   GtkWidget *widget;
782   GdkRectangle clip;
783   GtkStateType state;
784
785   widget = GTK_WIDGET (expander);
786
787   get_expander_bounds (expander, &clip);
788
789   state = gtk_widget_get_state (widget);
790   if (expander->priv->prelight)
791     {
792       state = GTK_STATE_PRELIGHT;
793
794       gtk_expander_paint_prelight (expander);
795     }
796
797   gtk_paint_expander (gtk_widget_get_style (widget),
798                       gtk_widget_get_window (widget),
799                       state,
800                       &clip,
801                       widget,
802                       "expander",
803                       clip.x + clip.width / 2,
804                       clip.y + clip.height / 2,
805                       expander->priv->expander_style);
806 }
807
808 static void
809 gtk_expander_paint_focus (GtkExpander  *expander,
810                           GdkRectangle *area)
811 {
812   GtkWidget *widget;
813   GtkExpanderPrivate *priv;
814   GdkRectangle rect;
815   gint x, y, width, height;
816   gboolean interior_focus;
817   gint border_width;
818   gint focus_width;
819   gint focus_pad;
820   gint expander_size;
821   gint expander_spacing;
822   gboolean ltr;
823
824   widget = GTK_WIDGET (expander);
825   priv = expander->priv;
826
827   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
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       GtkAllocation allocation;
844
845       gtk_widget_get_allocation (widget, &allocation);
846
847       if (gtk_widget_get_visible (priv->label_widget))
848         {
849           GtkAllocation label_allocation;
850
851           gtk_widget_get_allocation (priv->label_widget, &label_allocation);
852           width  = label_allocation.width;
853           height = label_allocation.height;
854         }
855
856       width  += 2 * focus_pad + 2 * focus_width;
857       height += 2 * focus_pad + 2 * focus_width;
858
859       x = allocation.x + border_width;
860       y = allocation.y + border_width;
861
862       if (ltr)
863         {
864           if (interior_focus)
865             x += expander_spacing * 2 + expander_size;
866         }
867       else
868         {
869           x += allocation.width - 2 * border_width
870             - expander_spacing * 2 - expander_size - width;
871         }
872
873       if (!interior_focus)
874         {
875           width += expander_size + 2 * expander_spacing;
876           height = MAX (height, expander_size + 2 * expander_spacing);
877         }
878     }
879   else
880     {
881       get_expander_bounds (expander, &rect);
882
883       x = rect.x - focus_pad;
884       y = rect.y - focus_pad;
885       width = rect.width + 2 * focus_pad;
886       height = rect.height + 2 * focus_pad;
887     }
888
889   gtk_paint_focus (gtk_widget_get_style (widget),
890                    gtk_widget_get_window (widget),
891                    gtk_widget_get_state (widget),
892                    area, widget, "expander",
893                    x, y, width, height);
894 }
895
896 static gboolean
897 gtk_expander_expose (GtkWidget      *widget,
898                      GdkEventExpose *event)
899 {
900   if (gtk_widget_is_drawable (widget))
901     {
902       GtkExpander *expander = GTK_EXPANDER (widget);
903
904       gtk_expander_paint (expander);
905
906       if (gtk_widget_has_focus (widget))
907         gtk_expander_paint_focus (expander, &event->area);
908
909       GTK_WIDGET_CLASS (gtk_expander_parent_class)->expose_event (widget, event);
910     }
911
912   return FALSE;
913 }
914
915 static gboolean
916 gtk_expander_button_press (GtkWidget      *widget,
917                            GdkEventButton *event)
918 {
919   GtkExpander *expander = GTK_EXPANDER (widget);
920
921   if (event->button == 1 && event->window == expander->priv->event_window)
922     {
923       expander->priv->button_down = TRUE;
924       return TRUE;
925     }
926
927   return FALSE;
928 }
929
930 static gboolean
931 gtk_expander_button_release (GtkWidget      *widget,
932                              GdkEventButton *event)
933 {
934   GtkExpander *expander = GTK_EXPANDER (widget);
935
936   if (event->button == 1 && expander->priv->button_down)
937     {
938       gtk_widget_activate (widget);
939       expander->priv->button_down = FALSE;
940       return TRUE;
941     }
942
943   return FALSE;
944 }
945
946 static void
947 gtk_expander_grab_notify (GtkWidget *widget,
948                           gboolean   was_grabbed)
949 {
950   if (!was_grabbed)
951     GTK_EXPANDER (widget)->priv->button_down = FALSE;
952 }
953
954 static void
955 gtk_expander_state_changed (GtkWidget    *widget,
956                             GtkStateType  previous_state)
957 {
958   if (!gtk_widget_is_sensitive (widget))
959     GTK_EXPANDER (widget)->priv->button_down = FALSE;
960 }
961
962 static void
963 gtk_expander_redraw_expander (GtkExpander *expander)
964 {
965   GtkAllocation allocation;
966   GtkWidget *widget = GTK_WIDGET (expander);
967
968   if (gtk_widget_get_realized (widget))
969     {
970       gtk_widget_get_allocation (widget, &allocation);
971       gdk_window_invalidate_rect (gtk_widget_get_window (widget), &allocation, FALSE);
972     }
973 }
974
975 static gboolean
976 gtk_expander_enter_notify (GtkWidget        *widget,
977                            GdkEventCrossing *event)
978 {
979   GtkExpander *expander = GTK_EXPANDER (widget);
980   GtkWidget *event_widget;
981
982   event_widget = gtk_get_event_widget ((GdkEvent *) event);
983
984   if (event_widget == widget &&
985       event->detail != GDK_NOTIFY_INFERIOR)
986     {
987       expander->priv->prelight = TRUE;
988
989       if (expander->priv->label_widget)
990         gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_PRELIGHT);
991
992       gtk_expander_redraw_expander (expander);
993     }
994
995   return FALSE;
996 }
997
998 static gboolean
999 gtk_expander_leave_notify (GtkWidget        *widget,
1000                            GdkEventCrossing *event)
1001 {
1002   GtkExpander *expander = GTK_EXPANDER (widget);
1003   GtkWidget *event_widget;
1004
1005   event_widget = gtk_get_event_widget ((GdkEvent *) event);
1006
1007   if (event_widget == widget &&
1008       event->detail != GDK_NOTIFY_INFERIOR)
1009     {
1010       expander->priv->prelight = FALSE;
1011
1012       if (expander->priv->label_widget)
1013         gtk_widget_set_state (expander->priv->label_widget, GTK_STATE_NORMAL);
1014
1015       gtk_expander_redraw_expander (expander);
1016     }
1017
1018   return FALSE;
1019 }
1020
1021 static gboolean
1022 expand_timeout (gpointer data)
1023 {
1024   GtkExpander *expander = GTK_EXPANDER (data);
1025   GtkExpanderPrivate *priv = expander->priv;
1026
1027   priv->expand_timer = 0;
1028   gtk_expander_set_expanded (expander, TRUE);
1029
1030   return FALSE;
1031 }
1032
1033 static gboolean
1034 gtk_expander_drag_motion (GtkWidget        *widget,
1035                           GdkDragContext   *context,
1036                           gint              x,
1037                           gint              y,
1038                           guint             time)
1039 {
1040   GtkExpander *expander = GTK_EXPANDER (widget);
1041   GtkExpanderPrivate *priv = expander->priv;
1042
1043   if (!priv->expanded && !priv->expand_timer)
1044     {
1045       GtkSettings *settings;
1046       guint timeout;
1047
1048       settings = gtk_widget_get_settings (widget);
1049       g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
1050
1051       priv->expand_timer = gdk_threads_add_timeout (timeout, (GSourceFunc) expand_timeout, expander);
1052     }
1053
1054   return TRUE;
1055 }
1056
1057 static void
1058 gtk_expander_drag_leave (GtkWidget      *widget,
1059                          GdkDragContext *context,
1060                          guint           time)
1061 {
1062   GtkExpander *expander = GTK_EXPANDER (widget);
1063   GtkExpanderPrivate *priv = expander->priv;
1064
1065   if (priv->expand_timer)
1066     {
1067       g_source_remove (priv->expand_timer);
1068       priv->expand_timer = 0;
1069     }
1070 }
1071
1072 typedef enum
1073 {
1074   FOCUS_NONE,
1075   FOCUS_WIDGET,
1076   FOCUS_LABEL,
1077   FOCUS_CHILD
1078 } FocusSite;
1079
1080 static gboolean
1081 focus_current_site (GtkExpander      *expander,
1082                     GtkDirectionType  direction)
1083 {
1084   GtkWidget *current_focus;
1085
1086   current_focus = gtk_container_get_focus_child (GTK_CONTAINER (expander));
1087
1088   if (!current_focus)
1089     return FALSE;
1090
1091   return gtk_widget_child_focus (current_focus, direction);
1092 }
1093
1094 static gboolean
1095 focus_in_site (GtkExpander      *expander,
1096                FocusSite         site,
1097                GtkDirectionType  direction)
1098 {
1099   switch (site)
1100     {
1101     case FOCUS_WIDGET:
1102       gtk_widget_grab_focus (GTK_WIDGET (expander));
1103       return TRUE;
1104     case FOCUS_LABEL:
1105       if (expander->priv->label_widget)
1106         return gtk_widget_child_focus (expander->priv->label_widget, direction);
1107       else
1108         return FALSE;
1109     case FOCUS_CHILD:
1110       {
1111         GtkWidget *child = gtk_bin_get_child (GTK_BIN (expander));
1112
1113         if (child && GTK_WIDGET_CHILD_VISIBLE (child))
1114           return gtk_widget_child_focus (child, direction);
1115         else
1116           return FALSE;
1117       }
1118     case FOCUS_NONE:
1119       break;
1120     }
1121
1122   g_assert_not_reached ();
1123   return FALSE;
1124 }
1125
1126 static FocusSite
1127 get_next_site (GtkExpander      *expander,
1128                FocusSite         site,
1129                GtkDirectionType  direction)
1130 {
1131   gboolean ltr;
1132
1133   ltr = gtk_widget_get_direction (GTK_WIDGET (expander)) != GTK_TEXT_DIR_RTL;
1134
1135   switch (site)
1136     {
1137     case FOCUS_NONE:
1138       switch (direction)
1139         {
1140         case GTK_DIR_TAB_BACKWARD:
1141         case GTK_DIR_LEFT:
1142         case GTK_DIR_UP:
1143           return FOCUS_CHILD;
1144         case GTK_DIR_TAB_FORWARD:
1145         case GTK_DIR_DOWN:
1146         case GTK_DIR_RIGHT:
1147           return FOCUS_WIDGET;
1148         }
1149     case FOCUS_WIDGET:
1150       switch (direction)
1151         {
1152         case GTK_DIR_TAB_BACKWARD:
1153         case GTK_DIR_UP:
1154           return FOCUS_NONE;
1155         case GTK_DIR_LEFT:
1156           return ltr ? FOCUS_NONE : FOCUS_LABEL;
1157         case GTK_DIR_TAB_FORWARD:
1158         case GTK_DIR_DOWN:
1159           return FOCUS_LABEL;
1160         case GTK_DIR_RIGHT:
1161           return ltr ? FOCUS_LABEL : FOCUS_NONE;
1162           break;
1163         }
1164     case FOCUS_LABEL:
1165       switch (direction)
1166         {
1167         case GTK_DIR_TAB_BACKWARD:
1168         case GTK_DIR_UP:
1169           return FOCUS_WIDGET;
1170         case GTK_DIR_LEFT:
1171           return ltr ? FOCUS_WIDGET : FOCUS_CHILD;
1172         case GTK_DIR_TAB_FORWARD:
1173         case GTK_DIR_DOWN:
1174           return FOCUS_CHILD;
1175         case GTK_DIR_RIGHT:
1176           return ltr ? FOCUS_CHILD : FOCUS_WIDGET;
1177           break;
1178         }
1179     case FOCUS_CHILD:
1180       switch (direction)
1181         {
1182         case GTK_DIR_TAB_BACKWARD:
1183         case GTK_DIR_LEFT:
1184         case GTK_DIR_UP:
1185           return FOCUS_LABEL;
1186         case GTK_DIR_TAB_FORWARD:
1187         case GTK_DIR_DOWN:
1188         case GTK_DIR_RIGHT:
1189           return FOCUS_NONE;
1190         }
1191     }
1192
1193   g_assert_not_reached ();
1194   return FOCUS_NONE;
1195 }
1196
1197 static gboolean
1198 gtk_expander_focus (GtkWidget        *widget,
1199                     GtkDirectionType  direction)
1200 {
1201   GtkExpander *expander = GTK_EXPANDER (widget);
1202   
1203   if (!focus_current_site (expander, direction))
1204     {
1205       GtkWidget *old_focus_child;
1206       gboolean widget_is_focus;
1207       FocusSite site = FOCUS_NONE;
1208       
1209       widget_is_focus = gtk_widget_is_focus (widget);
1210       old_focus_child = gtk_container_get_focus_child (GTK_CONTAINER (widget));
1211       
1212       if (old_focus_child && old_focus_child == expander->priv->label_widget)
1213         site = FOCUS_LABEL;
1214       else if (old_focus_child)
1215         site = FOCUS_CHILD;
1216       else if (widget_is_focus)
1217         site = FOCUS_WIDGET;
1218
1219       while ((site = get_next_site (expander, site, direction)) != FOCUS_NONE)
1220         {
1221           if (focus_in_site (expander, site, direction))
1222             return TRUE;
1223         }
1224
1225       return FALSE;
1226     }
1227
1228   return TRUE;
1229 }
1230
1231 static void
1232 gtk_expander_add (GtkContainer *container,
1233                   GtkWidget    *widget)
1234 {
1235   GTK_CONTAINER_CLASS (gtk_expander_parent_class)->add (container, widget);
1236
1237   gtk_widget_set_child_visible (widget, GTK_EXPANDER (container)->priv->expanded);
1238   gtk_widget_queue_resize (GTK_WIDGET (container));
1239 }
1240
1241 static void
1242 gtk_expander_remove (GtkContainer *container,
1243                      GtkWidget    *widget)
1244 {
1245   GtkExpander *expander = GTK_EXPANDER (container);
1246
1247   if (GTK_EXPANDER (expander)->priv->label_widget == widget)
1248     gtk_expander_set_label_widget (expander, NULL);
1249   else
1250     GTK_CONTAINER_CLASS (gtk_expander_parent_class)->remove (container, widget);
1251 }
1252
1253 static void
1254 gtk_expander_forall (GtkContainer *container,
1255                      gboolean      include_internals,
1256                      GtkCallback   callback,
1257                      gpointer      callback_data)
1258 {
1259   GtkBin *bin = GTK_BIN (container);
1260   GtkExpanderPrivate *priv = GTK_EXPANDER (container)->priv;
1261   GtkWidget *child;
1262
1263   child = gtk_bin_get_child (bin);
1264   if (child)
1265     (* callback) (child, callback_data);
1266
1267   if (priv->label_widget)
1268     (* callback) (priv->label_widget, callback_data);
1269 }
1270
1271 static void
1272 gtk_expander_activate (GtkExpander *expander)
1273 {
1274   gtk_expander_set_expanded (expander, !expander->priv->expanded);
1275 }
1276
1277
1278 static void
1279 gtk_expander_size_request_init (GtkSizeRequestIface *iface)
1280 {
1281   iface->get_width            = gtk_expander_get_width;
1282   iface->get_height           = gtk_expander_get_height;
1283   iface->get_height_for_width = gtk_expander_get_height_for_width;
1284   iface->get_width_for_height = gtk_expander_get_width_for_height;
1285 }
1286
1287 static void     
1288 gtk_expander_get_width (GtkSizeRequest      *widget,
1289                         gint                *minimum_size,
1290                         gint                *natural_size)
1291 {
1292   GtkExpander *expander;
1293   GtkWidget *child;
1294   GtkExpanderPrivate *priv;
1295   gint border_width;
1296   gint expander_size;
1297   gint expander_spacing;
1298   gboolean interior_focus;
1299   gint focus_width;
1300   gint focus_pad;
1301
1302   child = gtk_bin_get_child (GTK_BIN (widget));
1303   expander = GTK_EXPANDER (widget);
1304   priv = expander->priv;
1305
1306   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1307
1308   gtk_widget_style_get (GTK_WIDGET (widget),
1309                         "interior-focus", &interior_focus,
1310                         "focus-line-width", &focus_width,
1311                         "focus-padding", &focus_pad,
1312                         "expander-size", &expander_size,
1313                         "expander-spacing", &expander_spacing,
1314                         NULL);
1315
1316   *minimum_size = *natural_size = 
1317     expander_size + 2 * expander_spacing +
1318     2 * focus_width + 2 * focus_pad;
1319
1320   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1321     {
1322       gint label_min, label_nat;
1323
1324       gtk_size_request_get_width (GTK_SIZE_REQUEST (priv->label_widget), 
1325                                   &label_min, &label_nat);
1326
1327       *minimum_size += label_min;
1328       *natural_size += label_nat;
1329     }
1330
1331   if (child && GTK_WIDGET_CHILD_VISIBLE (child))
1332     {
1333       gint child_min, child_nat;
1334
1335       gtk_size_request_get_width (GTK_SIZE_REQUEST (child), 
1336                                   &child_min, &child_nat);
1337
1338       *minimum_size = MAX (*minimum_size, child_min);
1339       *natural_size = MAX (*natural_size, child_nat);
1340
1341     }
1342
1343   *minimum_size += 2 * border_width;
1344   *natural_size += 2 * border_width;
1345 }
1346
1347 static void
1348 gtk_expander_get_height (GtkSizeRequest      *widget,
1349                          gint                *minimum_size,
1350                          gint                *natural_size)
1351 {  
1352   GtkExpander *expander;
1353   GtkWidget *child;
1354   GtkExpanderPrivate *priv;
1355   gint border_width;
1356   gint expander_size;
1357   gint expander_spacing;
1358   gboolean interior_focus;
1359   gint focus_width;
1360   gint focus_pad;
1361
1362   child = gtk_bin_get_child (GTK_BIN (widget));
1363   expander = GTK_EXPANDER (widget);
1364   priv = expander->priv;
1365
1366   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1367
1368   gtk_widget_style_get (GTK_WIDGET (widget),
1369                         "interior-focus", &interior_focus,
1370                         "focus-line-width", &focus_width,
1371                         "focus-padding", &focus_pad,
1372                         "expander-size", &expander_size,
1373                         "expander-spacing", &expander_spacing,
1374                         NULL);
1375
1376   *minimum_size = *natural_size = 
1377     interior_focus ? (2 * focus_width + 2 * focus_pad) : 0;
1378
1379
1380   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1381     {
1382       gint label_min, label_nat;
1383
1384       gtk_size_request_get_height (GTK_SIZE_REQUEST (priv->label_widget), 
1385                                    &label_min, &label_nat);
1386       
1387       *minimum_size += label_min;
1388       *natural_size += label_nat;
1389     }
1390
1391   *minimum_size = MAX (*minimum_size, expander_size + 2 * expander_spacing);
1392   *natural_size = MAX (*natural_size, *minimum_size);
1393
1394   if (!interior_focus)
1395     {
1396       gint extra = 2 * focus_width + 2 * focus_pad;
1397       *minimum_size += extra;
1398       *natural_size += extra;
1399     }
1400
1401   if (child && GTK_WIDGET_CHILD_VISIBLE (child))
1402     {
1403       gint child_min, child_nat;
1404
1405       gtk_size_request_get_height (GTK_SIZE_REQUEST (child), 
1406                                    &child_min, &child_nat);
1407
1408       *minimum_size += child_min + priv->spacing;
1409       *natural_size += child_nat + priv->spacing;
1410
1411     }
1412
1413   *minimum_size += 2 * border_width;
1414   *natural_size += 2 * border_width;
1415 }
1416
1417 static void
1418 gtk_expander_get_height_for_width (GtkSizeRequest *widget,
1419                                    gint            width,
1420                                    gint           *minimum_height,
1421                                    gint           *natural_height)
1422 {
1423   GtkExpander *expander;
1424   GtkWidget *child;
1425   GtkExpanderPrivate *priv;
1426   gint border_width;
1427   gint expander_size;
1428   gint expander_spacing;
1429   gboolean interior_focus;
1430   gint focus_width;
1431   gint focus_pad;
1432   gint label_xpad;
1433
1434   child = gtk_bin_get_child (GTK_BIN (widget));
1435   expander = GTK_EXPANDER (widget);
1436   priv = expander->priv;
1437
1438   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1439
1440   gtk_widget_style_get (GTK_WIDGET (widget),
1441                         "interior-focus", &interior_focus,
1442                         "focus-line-width", &focus_width,
1443                         "focus-padding", &focus_pad,
1444                         "expander-size", &expander_size,
1445                         "expander-spacing", &expander_spacing,
1446                         NULL);
1447
1448   label_xpad = 2 * border_width + expander_size + 2 * expander_spacing - 2 * focus_width + 2 * focus_pad;
1449
1450   *minimum_height = *natural_height = 
1451     interior_focus ? (2 * focus_width + 2 * focus_pad) : 0;
1452
1453
1454   if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
1455     {
1456       gint label_min, label_nat;
1457
1458       gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (priv->label_widget), 
1459                                              MAX (width - label_xpad, 1), 
1460                                              &label_min, &label_nat);
1461       
1462       *minimum_height += label_min;
1463       *natural_height += label_nat;
1464     }
1465
1466   *minimum_height = MAX (*minimum_height, expander_size + 2 * expander_spacing);
1467   *natural_height = MAX (*natural_height, *minimum_height);
1468
1469   if (!interior_focus)
1470     {
1471       gint extra = 2 * focus_width + 2 * focus_pad;
1472       *minimum_height += extra;
1473       *natural_height += extra;
1474     }
1475
1476   if (child && GTK_WIDGET_CHILD_VISIBLE (child))
1477     {
1478       gint child_min, child_nat;
1479
1480       gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (child), 
1481                                              MAX (width - 2 * border_width, 1), 
1482                                              &child_min, &child_nat);
1483
1484       *minimum_height += child_min + priv->spacing;
1485       *natural_height += child_nat + priv->spacing;
1486     }
1487
1488   *minimum_height += 2 * border_width;
1489   *natural_height += 2 * border_width;
1490 }
1491
1492 static void
1493 gtk_expander_get_width_for_height (GtkSizeRequest *widget,
1494                                    gint       height,
1495                                    gint      *minimum_width,
1496                                    gint      *natural_width)
1497 {
1498   GTK_SIZE_REQUEST_GET_IFACE (widget)->get_width (widget, minimum_width, natural_width);
1499 }
1500
1501
1502
1503 /**
1504  * gtk_expander_new:
1505  * @label: the text of the label
1506  * 
1507  * Creates a new expander using @label as the text of the label.
1508  * 
1509  * Return value: a new #GtkExpander widget.
1510  *
1511  * Since: 2.4
1512  **/
1513 GtkWidget *
1514 gtk_expander_new (const gchar *label)
1515 {
1516   return g_object_new (GTK_TYPE_EXPANDER, "label", label, NULL);
1517 }
1518
1519 /**
1520  * gtk_expander_new_with_mnemonic:
1521  * @label: (allow-none): the text of the label with an underscore in front of the
1522  *         mnemonic character
1523  *
1524  * Creates a new expander using @label as the text of the label.
1525  * If characters in @label are preceded by an underscore, they are underlined.
1526  * If you need a literal underscore character in a label, use '__' (two 
1527  * underscores). The first underlined character represents a keyboard 
1528  * accelerator called a mnemonic.
1529  * Pressing Alt and that key activates the button.
1530  * 
1531  * Return value: a new #GtkExpander widget.
1532  *
1533  * Since: 2.4
1534  **/
1535 GtkWidget *
1536 gtk_expander_new_with_mnemonic (const gchar *label)
1537 {
1538   return g_object_new (GTK_TYPE_EXPANDER,
1539                        "label", label,
1540                        "use-underline", TRUE,
1541                        NULL);
1542 }
1543
1544 static gboolean
1545 gtk_expander_animation_timeout (GtkExpander *expander)
1546 {
1547   GtkExpanderPrivate *priv = expander->priv;
1548   GtkWidget *widget = GTK_WIDGET (expander);
1549   GtkWidget *child;
1550   GdkRectangle area;
1551   gboolean finish = FALSE;
1552
1553   if (gtk_widget_get_realized (widget))
1554     {
1555       get_expander_bounds (expander, &area);
1556       gdk_window_invalidate_rect (gtk_widget_get_window (widget), &area, TRUE);
1557     }
1558
1559   if (priv->expanded)
1560     {
1561       if (priv->expander_style == GTK_EXPANDER_COLLAPSED)
1562         {
1563           priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED;
1564         }
1565       else
1566         {
1567           priv->expander_style = GTK_EXPANDER_EXPANDED;
1568           finish = TRUE;
1569         }
1570     }
1571   else
1572     {
1573       if (priv->expander_style == GTK_EXPANDER_EXPANDED)
1574         {
1575           priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED;
1576         }
1577       else
1578         {
1579           priv->expander_style = GTK_EXPANDER_COLLAPSED;
1580           finish = TRUE;
1581         }
1582     }
1583
1584   if (finish)
1585     {
1586       priv->animation_timeout = 0;
1587
1588       child = gtk_bin_get_child (GTK_BIN (expander));
1589       if (child)
1590         gtk_widget_set_child_visible (child, priv->expanded);
1591       gtk_widget_queue_resize (widget);
1592     }
1593
1594   return !finish;
1595 }
1596
1597 static void
1598 gtk_expander_start_animation (GtkExpander *expander)
1599 {
1600   GtkExpanderPrivate *priv = expander->priv;
1601
1602   if (priv->animation_timeout)
1603     g_source_remove (priv->animation_timeout);
1604
1605   priv->animation_timeout =
1606                 gdk_threads_add_timeout (50,
1607                                (GSourceFunc) gtk_expander_animation_timeout,
1608                                expander);
1609 }
1610
1611 /**
1612  * gtk_expander_set_expanded:
1613  * @expander: a #GtkExpander
1614  * @expanded: whether the child widget is revealed
1615  *
1616  * Sets the state of the expander. Set to %TRUE, if you want
1617  * the child widget to be revealed, and %FALSE if you want the
1618  * child widget to be hidden.
1619  *
1620  * Since: 2.4
1621  **/
1622 void
1623 gtk_expander_set_expanded (GtkExpander *expander,
1624                            gboolean     expanded)
1625 {
1626   GtkExpanderPrivate *priv;
1627   GtkWidget *child;
1628
1629   g_return_if_fail (GTK_IS_EXPANDER (expander));
1630
1631   priv = expander->priv;
1632
1633   expanded = expanded != FALSE;
1634
1635   if (priv->expanded != expanded)
1636     {
1637       GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (expander));
1638       gboolean     enable_animations;
1639
1640       priv->expanded = expanded;
1641
1642       g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
1643
1644       if (enable_animations && gtk_widget_get_realized (GTK_WIDGET (expander)))
1645         {
1646           gtk_expander_start_animation (expander);
1647         }
1648       else
1649         {
1650           priv->expander_style = expanded ? GTK_EXPANDER_EXPANDED :
1651                                             GTK_EXPANDER_COLLAPSED;
1652
1653           child = gtk_bin_get_child (GTK_BIN (expander));
1654           if (child)
1655             {
1656               gtk_widget_set_child_visible (child, priv->expanded);
1657               gtk_widget_queue_resize (GTK_WIDGET (expander));
1658             }
1659         }
1660
1661       g_object_notify (G_OBJECT (expander), "expanded");
1662     }
1663 }
1664
1665 /**
1666  * gtk_expander_get_expanded:
1667  * @expander:a #GtkExpander
1668  *
1669  * Queries a #GtkExpander and returns its current state. Returns %TRUE
1670  * if the child widget is revealed.
1671  *
1672  * See gtk_expander_set_expanded().
1673  *
1674  * Return value: the current state of the expander.
1675  *
1676  * Since: 2.4
1677  **/
1678 gboolean
1679 gtk_expander_get_expanded (GtkExpander *expander)
1680 {
1681   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1682
1683   return expander->priv->expanded;
1684 }
1685
1686 /**
1687  * gtk_expander_set_spacing:
1688  * @expander: a #GtkExpander
1689  * @spacing: distance between the expander and child in pixels.
1690  *
1691  * Sets the spacing field of @expander, which is the number of pixels to
1692  * place between expander and the child.
1693  *
1694  * Since: 2.4
1695  **/
1696 void
1697 gtk_expander_set_spacing (GtkExpander *expander,
1698                           gint         spacing)
1699 {
1700   g_return_if_fail (GTK_IS_EXPANDER (expander));
1701   g_return_if_fail (spacing >= 0);
1702
1703   if (expander->priv->spacing != spacing)
1704     {
1705       expander->priv->spacing = spacing;
1706
1707       gtk_widget_queue_resize (GTK_WIDGET (expander));
1708
1709       g_object_notify (G_OBJECT (expander), "spacing");
1710     }
1711 }
1712
1713 /**
1714  * gtk_expander_get_spacing:
1715  * @expander: a #GtkExpander
1716  *
1717  * Gets the value set by gtk_expander_set_spacing().
1718  *
1719  * Return value: spacing between the expander and child.
1720  *
1721  * Since: 2.4
1722  **/
1723 gint
1724 gtk_expander_get_spacing (GtkExpander *expander)
1725 {
1726   g_return_val_if_fail (GTK_IS_EXPANDER (expander), 0);
1727
1728   return expander->priv->spacing;
1729 }
1730
1731 /**
1732  * gtk_expander_set_label:
1733  * @expander: a #GtkExpander
1734  * @label: (allow-none): a string
1735  *
1736  * Sets the text of the label of the expander to @label.
1737  *
1738  * This will also clear any previously set labels.
1739  *
1740  * Since: 2.4
1741  **/
1742 void
1743 gtk_expander_set_label (GtkExpander *expander,
1744                         const gchar *label)
1745 {
1746   g_return_if_fail (GTK_IS_EXPANDER (expander));
1747
1748   if (!label)
1749     {
1750       gtk_expander_set_label_widget (expander, NULL);
1751     }
1752   else
1753     {
1754       GtkWidget *child;
1755
1756       child = gtk_label_new (label);
1757       gtk_label_set_use_underline (GTK_LABEL (child), expander->priv->use_underline);
1758       gtk_label_set_use_markup (GTK_LABEL (child), expander->priv->use_markup);
1759       gtk_widget_show (child);
1760
1761       gtk_expander_set_label_widget (expander, child);
1762     }
1763
1764   g_object_notify (G_OBJECT (expander), "label");
1765 }
1766
1767 /**
1768  * gtk_expander_get_label:
1769  * @expander: a #GtkExpander
1770  *
1771  * Fetches the text from a label widget including any embedded
1772  * underlines indicating mnemonics and Pango markup, as set by
1773  * gtk_expander_set_label(). If the label text has not been set the
1774  * return value will be %NULL. This will be the case if you create an
1775  * empty button with gtk_button_new() to use as a container.
1776  *
1777  * Note that this function behaved differently in versions prior to
1778  * 2.14 and used to return the label text stripped of embedded
1779  * underlines indicating mnemonics and Pango markup. This problem can
1780  * be avoided by fetching the label text directly from the label
1781  * widget.
1782  *
1783  * Return value: The text of the label widget. This string is owned
1784  * by the widget and must not be modified or freed.
1785  *
1786  * Since: 2.4
1787  **/
1788 G_CONST_RETURN char *
1789 gtk_expander_get_label (GtkExpander *expander)
1790 {
1791   GtkExpanderPrivate *priv;
1792
1793   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1794
1795   priv = expander->priv;
1796
1797   if (GTK_IS_LABEL (priv->label_widget))
1798     return gtk_label_get_label (GTK_LABEL (priv->label_widget));
1799   else
1800     return NULL;
1801 }
1802
1803 /**
1804  * gtk_expander_set_use_underline:
1805  * @expander: a #GtkExpander
1806  * @use_underline: %TRUE if underlines in the text indicate mnemonics
1807  *
1808  * If true, an underline in the text of the expander label indicates
1809  * the next character should be used for the mnemonic accelerator key.
1810  *
1811  * Since: 2.4
1812  **/
1813 void
1814 gtk_expander_set_use_underline (GtkExpander *expander,
1815                                 gboolean     use_underline)
1816 {
1817   GtkExpanderPrivate *priv;
1818
1819   g_return_if_fail (GTK_IS_EXPANDER (expander));
1820
1821   priv = expander->priv;
1822
1823   use_underline = use_underline != FALSE;
1824
1825   if (priv->use_underline != use_underline)
1826     {
1827       priv->use_underline = use_underline;
1828
1829       if (GTK_IS_LABEL (priv->label_widget))
1830         gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), use_underline);
1831
1832       g_object_notify (G_OBJECT (expander), "use-underline");
1833     }
1834 }
1835
1836 /**
1837  * gtk_expander_get_use_underline:
1838  * @expander: a #GtkExpander
1839  *
1840  * Returns whether an embedded underline in the expander label indicates a
1841  * mnemonic. See gtk_expander_set_use_underline().
1842  *
1843  * Return value: %TRUE if an embedded underline in the expander label
1844  *               indicates the mnemonic accelerator keys.
1845  *
1846  * Since: 2.4
1847  **/
1848 gboolean
1849 gtk_expander_get_use_underline (GtkExpander *expander)
1850 {
1851   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1852
1853   return expander->priv->use_underline;
1854 }
1855
1856 /**
1857  * gtk_expander_set_use_markup:
1858  * @expander: a #GtkExpander
1859  * @use_markup: %TRUE if the label's text should be parsed for markup
1860  *
1861  * Sets whether the text of the label contains markup in <link
1862  * linkend="PangoMarkupFormat">Pango's text markup
1863  * language</link>. See gtk_label_set_markup().
1864  *
1865  * Since: 2.4
1866  **/
1867 void
1868 gtk_expander_set_use_markup (GtkExpander *expander,
1869                              gboolean     use_markup)
1870 {
1871   GtkExpanderPrivate *priv;
1872
1873   g_return_if_fail (GTK_IS_EXPANDER (expander));
1874
1875   priv = expander->priv;
1876
1877   use_markup = use_markup != FALSE;
1878
1879   if (priv->use_markup != use_markup)
1880     {
1881       priv->use_markup = use_markup;
1882
1883       if (GTK_IS_LABEL (priv->label_widget))
1884         gtk_label_set_use_markup (GTK_LABEL (priv->label_widget), use_markup);
1885
1886       g_object_notify (G_OBJECT (expander), "use-markup");
1887     }
1888 }
1889
1890 /**
1891  * gtk_expander_get_use_markup:
1892  * @expander: a #GtkExpander
1893  *
1894  * Returns whether the label's text is interpreted as marked up with
1895  * the <link linkend="PangoMarkupFormat">Pango text markup
1896  * language</link>. See gtk_expander_set_use_markup ().
1897  *
1898  * Return value: %TRUE if the label's text will be parsed for markup
1899  *
1900  * Since: 2.4
1901  **/
1902 gboolean
1903 gtk_expander_get_use_markup (GtkExpander *expander)
1904 {
1905   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
1906
1907   return expander->priv->use_markup;
1908 }
1909
1910 /**
1911  * gtk_expander_set_label_widget:
1912  * @expander: a #GtkExpander
1913  * @label_widget: (allow-none): the new label widget
1914  *
1915  * Set the label widget for the expander. This is the widget
1916  * that will appear embedded alongside the expander arrow.
1917  *
1918  * Since: 2.4
1919  **/
1920 void
1921 gtk_expander_set_label_widget (GtkExpander *expander,
1922                                GtkWidget   *label_widget)
1923 {
1924   GtkExpanderPrivate *priv;
1925   GtkWidget          *widget;
1926
1927   g_return_if_fail (GTK_IS_EXPANDER (expander));
1928   g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
1929   g_return_if_fail (label_widget == NULL || gtk_widget_get_parent (label_widget) == NULL);
1930
1931   priv = expander->priv;
1932
1933   if (priv->label_widget == label_widget)
1934     return;
1935
1936   if (priv->label_widget)
1937     {
1938       gtk_widget_set_state (priv->label_widget, GTK_STATE_NORMAL);
1939       gtk_widget_unparent (priv->label_widget);
1940     }
1941
1942   priv->label_widget = label_widget;
1943   widget = GTK_WIDGET (expander);
1944
1945   if (label_widget)
1946     {
1947       priv->label_widget = label_widget;
1948
1949       gtk_widget_set_parent (label_widget, widget);
1950
1951       if (priv->prelight)
1952         gtk_widget_set_state (label_widget, GTK_STATE_PRELIGHT);
1953     }
1954
1955   if (gtk_widget_get_visible (widget))
1956     gtk_widget_queue_resize (widget);
1957
1958   g_object_freeze_notify (G_OBJECT (expander));
1959   g_object_notify (G_OBJECT (expander), "label-widget");
1960   g_object_notify (G_OBJECT (expander), "label");
1961   g_object_thaw_notify (G_OBJECT (expander));
1962 }
1963
1964 /**
1965  * gtk_expander_get_label_widget:
1966  * @expander: a #GtkExpander
1967  *
1968  * Retrieves the label widget for the frame. See
1969  * gtk_expander_set_label_widget().
1970  *
1971  * Return value: the label widget, or %NULL if there is none.
1972  * 
1973  * Since: 2.4
1974  **/
1975 GtkWidget *
1976 gtk_expander_get_label_widget (GtkExpander *expander)
1977 {
1978   g_return_val_if_fail (GTK_IS_EXPANDER (expander), NULL);
1979
1980   return expander->priv->label_widget;
1981 }
1982
1983 /**
1984  * gtk_expander_set_label_fill:
1985  * @expander: a #GtkExpander
1986  * @label_fill: %TRUE if the label should should fill all available horizontal
1987  *              space
1988  *
1989  * Sets whether the label widget should fill all available horizontal space
1990  * allocated to @expander.
1991  *
1992  * Since: 2.22
1993  */
1994 void
1995 gtk_expander_set_label_fill (GtkExpander *expander,
1996                              gboolean     label_fill)
1997 {
1998   GtkExpanderPrivate *priv;
1999
2000   g_return_if_fail (GTK_IS_EXPANDER (expander));
2001
2002   priv = expander->priv;
2003
2004   label_fill = label_fill != FALSE;
2005
2006   if (priv->label_fill != label_fill)
2007     {
2008       priv->label_fill = label_fill;
2009
2010       if (priv->label_widget != NULL)
2011         gtk_widget_queue_resize (GTK_WIDGET (expander));
2012
2013       g_object_notify (G_OBJECT (expander), "label-fill");
2014     }
2015 }
2016
2017 /**
2018  * gtk_expander_get_label_fill:
2019  * @expander: a #GtkExpander
2020  *
2021  * Returns whether the label widget will fill all available horizontal
2022  * space allocated to @expander.
2023  *
2024  * Return value: %TRUE if the label widget will fill all available horizontal
2025  *               space
2026  *
2027  * Since: 2.22
2028  */
2029 gboolean
2030 gtk_expander_get_label_fill (GtkExpander *expander)
2031 {
2032   g_return_val_if_fail (GTK_IS_EXPANDER (expander), FALSE);
2033
2034   return expander->priv->label_fill;
2035 }
2036