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