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