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