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