gtkexpander.c 64.9 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3 4 5 6 7 8 9 10 11 12 13 14 15
 *
 * Copyright (C) 2003 Sun Microsystems, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
Javier Jardón's avatar
Javier Jardón committed
16
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 18
 *
 * Authors:
19 20 21 22 23 24 25 26 27 28
 *      Mark McLoughlin <mark@skynet.ie>
 */

/**
 * SECTION:gtkexpander
 * @Short_description: A container which can hide its child
 * @Title: GtkExpander
 *
 * A #GtkExpander allows the user to hide or show its child by clicking
 * on an expander triangle similar to the triangles used in a #GtkTreeView.
29
 *
30 31 32 33
 * Normally you use an expander as you would use any other descendant
 * of #GtkBin; you create the child widget and use gtk_container_add()
 * to add it to the expander. When the expander is toggled, it will take
 * care of showing and hiding the child automatically.
34
 *
35
 * # Special Usage
Matthias Clasen's avatar
Matthias Clasen committed
36
 *
37 38 39 40 41 42 43
 * There are situations in which you may prefer to show and hide the
 * expanded widget yourself, such as when you want to actually create
 * the widget at expansion time. In this case, create a #GtkExpander
 * but do not add a child to it. The expander widget has an
 * #GtkExpander:expanded property which can be used to monitor
 * its expansion state. You should watch this property with a signal
 * connection as follows:
Matthias Clasen's avatar
Matthias Clasen committed
44
 *
45
 * |[<!-- language="C" -->
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
 * expander = gtk_expander_new_with_mnemonic ("_More Options");
 * g_signal_connect (expander, "notify::expanded",
 *                   G_CALLBACK (expander_callback), NULL);
 *
 * ...
 *
 * static void
 * expander_callback (GObject    *object,
 *                    GParamSpec *param_spec,
 *                    gpointer    user_data)
 * {
 *   GtkExpander *expander;
 *
 *   expander = GTK_EXPANDER (object);
 *
 *   if (gtk_expander_get_expanded (expander))
 *     {
 *       /&ast; Show or create widgets &ast;/
 *     }
 *   else
 *     {
 *       /&ast; Hide or destroy widgets &ast;/
 *     }
 * }
70
 * ]|
Matthias Clasen's avatar
Matthias Clasen committed
71
 *
72
 * # GtkExpander as GtkBuildable
Matthias Clasen's avatar
Matthias Clasen committed
73
 *
74 75
 * The GtkExpander implementation of the GtkBuildable interface
 * supports placing a child in the label position by specifying
William Jon McCann's avatar
William Jon McCann committed
76
 * “label” as the “type” attribute of a &lt;child&gt; element.
77 78
 * A normal content child can be specified without specifying
 * a &lt;child&gt; type attribute.
Matthias Clasen's avatar
Matthias Clasen committed
79 80
 *
 * An example of a UI definition fragment with GtkExpander:
81
 * |[
82 83 84 85 86 87 88 89
 * <object class="GtkExpander">
 *   <child type="label">
 *     <object class="GtkLabel" id="expander-label"/>
 *   </child>
 *   <child>
 *     <object class="GtkEntry" id="expander-content"/>
 *   </child>
 * </object>
90
 * ]|
91 92
 */

93
#include "config.h"
94

Johan Dahlin's avatar
Johan Dahlin committed
95
#include <string.h>
96

97 98 99
#include "gtkexpander.h"

#include "gtklabel.h"
Johan Dahlin's avatar
Johan Dahlin committed
100
#include "gtkbuildable.h"
101 102 103 104 105
#include "gtkcontainer.h"
#include "gtkmarshalers.h"
#include "gtkmain.h"
#include "gtkintl.h"
#include "gtkprivate.h"
106
#include "gtkdnd.h"
107
#include "a11y/gtkexpanderaccessible.h"
108 109 110 111


#define DEFAULT_EXPANDER_SIZE 10
#define DEFAULT_EXPANDER_SPACING 2
112
#define TIMEOUT_EXPAND 500
113 114 115 116 117 118 119

enum
{
  PROP_0,
  PROP_EXPANDED,
  PROP_LABEL,
  PROP_USE_UNDERLINE,
120
  PROP_USE_MARKUP,
Soeren Sandmann's avatar
Soeren Sandmann committed
121
  PROP_SPACING,
122
  PROP_LABEL_WIDGET,
123 124
  PROP_LABEL_FILL,
  PROP_RESIZE_TOPLEVEL
125 126 127 128 129 130 131 132
};

struct _GtkExpanderPrivate
{
  GtkWidget        *label_widget;
  GdkWindow        *event_window;
  gint              spacing;

133
  guint             expand_timer;
134 135 136

  guint             expanded : 1;
  guint             use_underline : 1;
137
  guint             use_markup : 1; 
138 139
  guint             button_down : 1;
  guint             prelight : 1;
140
  guint             label_fill : 1;
141
  guint             resize_toplevel : 1;
142 143 144
};

static void gtk_expander_set_property (GObject          *object,
145 146 147
                                       guint             prop_id,
                                       const GValue     *value,
                                       GParamSpec       *pspec);
148
static void gtk_expander_get_property (GObject          *object,
149 150 151
                                       guint             prop_id,
                                       GValue           *value,
                                       GParamSpec       *pspec);
152

153
static void     gtk_expander_destroy        (GtkWidget        *widget);
154 155 156
static void     gtk_expander_realize        (GtkWidget        *widget);
static void     gtk_expander_unrealize      (GtkWidget        *widget);
static void     gtk_expander_size_allocate  (GtkWidget        *widget,
157
                                             GtkAllocation    *allocation);
158 159
static void     gtk_expander_map            (GtkWidget        *widget);
static void     gtk_expander_unmap          (GtkWidget        *widget);
Benjamin Otte's avatar
Benjamin Otte committed
160
static gboolean gtk_expander_draw           (GtkWidget        *widget,
161
                                             cairo_t          *cr);
162
static gboolean gtk_expander_button_press   (GtkWidget        *widget,
163
                                             GdkEventButton   *event);
164
static gboolean gtk_expander_button_release (GtkWidget        *widget,
165
                                             GdkEventButton   *event);
166
static gboolean gtk_expander_enter_notify   (GtkWidget        *widget,
167
                                             GdkEventCrossing *event);
168
static gboolean gtk_expander_leave_notify   (GtkWidget        *widget,
169
                                             GdkEventCrossing *event);
170
static gboolean gtk_expander_focus          (GtkWidget        *widget,
171
                                             GtkDirectionType  direction);
172
static void     gtk_expander_grab_notify    (GtkWidget        *widget,
173
                                             gboolean          was_grabbed);
174 175
static void     gtk_expander_state_flags_changed  (GtkWidget     *widget,
                                                   GtkStateFlags  previous_state);
176
static gboolean gtk_expander_drag_motion    (GtkWidget        *widget,
177 178 179 180
                                             GdkDragContext   *context,
                                             gint              x,
                                             gint              y,
                                             guint             time);
181
static void     gtk_expander_drag_leave     (GtkWidget        *widget,
182 183
                                             GdkDragContext   *context,
                                             guint             time);
184 185

static void gtk_expander_add    (GtkContainer *container,
186
                                 GtkWidget    *widget);
187
static void gtk_expander_remove (GtkContainer *container,
188
                                 GtkWidget    *widget);
189
static void gtk_expander_forall (GtkContainer *container,
190 191 192
                                 gboolean        include_internals,
                                 GtkCallback     callback,
                                 gpointer        callback_data);
193 194 195

static void gtk_expander_activate (GtkExpander *expander);

196
static void get_expander_bounds (GtkExpander  *expander,
197
                                 GdkRectangle *rect);
198

Johan Dahlin's avatar
Johan Dahlin committed
199 200
/* GtkBuildable */
static void gtk_expander_buildable_init           (GtkBuildableIface *iface);
201
static void gtk_expander_buildable_add_child      (GtkBuildable *buildable,
202 203 204
                                                   GtkBuilder   *builder,
                                                   GObject      *child,
                                                   const gchar  *type);
Johan Dahlin's avatar
Johan Dahlin committed
205

206

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
/* GtkWidget      */
static void  gtk_expander_get_preferred_width             (GtkWidget           *widget,
                                                           gint                *minimum_size,
                                                           gint                *natural_size);
static void  gtk_expander_get_preferred_height            (GtkWidget           *widget,
                                                           gint                *minimum_size,
                                                           gint                *natural_size);
static void  gtk_expander_get_preferred_height_for_width  (GtkWidget           *layout,
                                                           gint                 width,
                                                           gint                *minimum_height,
                                                           gint                *natural_height);
static void  gtk_expander_get_preferred_width_for_height  (GtkWidget           *layout,
                                                           gint                 width,
                                                           gint                *minimum_height,
                                                           gint                *natural_height);
222

Johan Dahlin's avatar
Johan Dahlin committed
223
G_DEFINE_TYPE_WITH_CODE (GtkExpander, gtk_expander, GTK_TYPE_BIN,
224
                         G_ADD_PRIVATE (GtkExpander)
225 226
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_expander_buildable_init))
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241

static void
gtk_expander_class_init (GtkExpanderClass *klass)
{
  GObjectClass *gobject_class;
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;

  gobject_class   = (GObjectClass *) klass;
  widget_class    = (GtkWidgetClass *) klass;
  container_class = (GtkContainerClass *) klass;

  gobject_class->set_property = gtk_expander_set_property;
  gobject_class->get_property = gtk_expander_get_property;

242
  widget_class->destroy              = gtk_expander_destroy;
243 244 245 246 247
  widget_class->realize              = gtk_expander_realize;
  widget_class->unrealize            = gtk_expander_unrealize;
  widget_class->size_allocate        = gtk_expander_size_allocate;
  widget_class->map                  = gtk_expander_map;
  widget_class->unmap                = gtk_expander_unmap;
Benjamin Otte's avatar
Benjamin Otte committed
248
  widget_class->draw                 = gtk_expander_draw;
249 250 251 252 253 254
  widget_class->button_press_event   = gtk_expander_button_press;
  widget_class->button_release_event = gtk_expander_button_release;
  widget_class->enter_notify_event   = gtk_expander_enter_notify;
  widget_class->leave_notify_event   = gtk_expander_leave_notify;
  widget_class->focus                = gtk_expander_focus;
  widget_class->grab_notify          = gtk_expander_grab_notify;
255
  widget_class->state_flags_changed  = gtk_expander_state_flags_changed;
256 257
  widget_class->drag_motion          = gtk_expander_drag_motion;
  widget_class->drag_leave           = gtk_expander_drag_leave;
258 259 260 261
  widget_class->get_preferred_width            = gtk_expander_get_preferred_width;
  widget_class->get_preferred_height           = gtk_expander_get_preferred_height;
  widget_class->get_preferred_height_for_width = gtk_expander_get_preferred_height_for_width;
  widget_class->get_preferred_width_for_height = gtk_expander_get_preferred_width_for_height;
262 263 264 265 266 267 268 269

  container_class->add    = gtk_expander_add;
  container_class->remove = gtk_expander_remove;
  container_class->forall = gtk_expander_forall;

  klass->activate = gtk_expander_activate;

  g_object_class_install_property (gobject_class,
270 271 272 273 274 275
                                   PROP_EXPANDED,
                                   g_param_spec_boolean ("expanded",
                                                         P_("Expanded"),
                                                         P_("Whether the expander has been opened to reveal the child widget"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
276 277

  g_object_class_install_property (gobject_class,
278 279 280 281 282 283
                                   PROP_LABEL,
                                   g_param_spec_string ("label",
                                                        P_("Label"),
                                                        P_("Text of the expander's label"),
                                                        NULL,
                                                        GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
284 285

  g_object_class_install_property (gobject_class,
286 287 288 289 290 291
                                   PROP_USE_UNDERLINE,
                                   g_param_spec_boolean ("use-underline",
                                                         P_("Use underline"),
                                                         P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
292

293
  g_object_class_install_property (gobject_class,
294 295 296 297 298 299
                                   PROP_USE_MARKUP,
                                   g_param_spec_boolean ("use-markup",
                                                         P_("Use markup"),
                                                         P_("The text of the label includes XML markup. See pango_parse_markup()"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
300

301
  g_object_class_install_property (gobject_class,
302 303 304 305 306 307 308 309
                                   PROP_SPACING,
                                   g_param_spec_int ("spacing",
                                                     P_("Spacing"),
                                                     P_("Space to put between the label and the child"),
                                                     0,
                                                     G_MAXINT,
                                                     0,
                                                     GTK_PARAM_READWRITE));
310 311

  g_object_class_install_property (gobject_class,
312 313 314 315 316 317
                                   PROP_LABEL_WIDGET,
                                   g_param_spec_object ("label-widget",
                                                        P_("Label widget"),
                                                        P_("A widget to display in place of the usual expander label"),
                                                        GTK_TYPE_WIDGET,
                                                        GTK_PARAM_READWRITE));
318

319
  g_object_class_install_property (gobject_class,
320 321 322 323 324 325
                                   PROP_LABEL_FILL,
                                   g_param_spec_boolean ("label-fill",
                                                         P_("Label fill"),
                                                         P_("Whether the label widget should fill all available horizontal space"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
326

327 328 329 330 331 332 333 334 335 336 337
  /**
   * GtkExpander:resize-toplevel:
   *
   * When this property is %TRUE, the expander will resize the toplevel
   * widget containing the expander upon expanding and collapsing.
   *
   * Since: 3.2
   */
  g_object_class_install_property (gobject_class,
                                   PROP_RESIZE_TOPLEVEL,
                                   g_param_spec_boolean ("resize-toplevel",
Rachid BM's avatar
Rachid BM committed
338
                                                         P_("Resize toplevel"),
339 340 341 342
                                                         P_("Whether the expander will resize the toplevel window upon expanding and collapsing"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

343
  gtk_widget_class_install_style_property (widget_class,
344 345 346 347 348 349 350
                                           g_param_spec_int ("expander-size",
                                                             P_("Expander Size"),
                                                             P_("Size of the expander arrow"),
                                                             0,
                                                             G_MAXINT,
                                                             DEFAULT_EXPANDER_SIZE,
                                                             GTK_PARAM_READABLE));
351 352

  gtk_widget_class_install_style_property (widget_class,
353 354 355 356 357 358 359
                                           g_param_spec_int ("expander-spacing",
                                                             P_("Indicator Spacing"),
                                                             P_("Spacing around expander arrow"),
                                                             0,
                                                             G_MAXINT,
                                                             DEFAULT_EXPANDER_SPACING,
                                                             GTK_PARAM_READABLE));
360 361

  widget_class->activate_signal =
Matthias Clasen's avatar
Matthias Clasen committed
362
    g_signal_new (I_("activate"),
363 364 365 366 367 368
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkExpanderClass, activate),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
369 370

  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_EXPANDER_ACCESSIBLE);
371 372 373 374 375 376 377
}

static void
gtk_expander_init (GtkExpander *expander)
{
  GtkExpanderPrivate *priv;

378
  expander->priv = priv = gtk_expander_get_instance_private (expander);
379

380
  gtk_widget_set_can_focus (GTK_WIDGET (expander), TRUE);
381
  gtk_widget_set_has_window (GTK_WIDGET (expander), FALSE);
382 383 384 385 386 387 388

  priv->label_widget = NULL;
  priv->event_window = NULL;
  priv->spacing = 0;

  priv->expanded = FALSE;
  priv->use_underline = FALSE;
389
  priv->use_markup = FALSE;
390 391
  priv->button_down = FALSE;
  priv->prelight = FALSE;
392
  priv->label_fill = FALSE;
393
  priv->expand_timer = 0;
394
  priv->resize_toplevel = 0;
395 396 397

  gtk_drag_dest_set (GTK_WIDGET (expander), 0, NULL, 0, 0);
  gtk_drag_dest_set_track_motion (GTK_WIDGET (expander), TRUE);
398 399
}

Johan Dahlin's avatar
Johan Dahlin committed
400
static void
401
gtk_expander_buildable_add_child (GtkBuildable  *buildable,
402 403 404
                                  GtkBuilder    *builder,
                                  GObject       *child,
                                  const gchar   *type)
Johan Dahlin's avatar
Johan Dahlin committed
405 406 407 408 409 410 411 412 413 414 415 416
{
  if (!type)
    gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
  else if (strcmp (type, "label") == 0)
    gtk_expander_set_label_widget (GTK_EXPANDER (buildable), GTK_WIDGET (child));
  else
    GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GTK_EXPANDER (buildable), type);
}

static void
gtk_expander_buildable_init (GtkBuildableIface *iface)
{
417
  iface->add_child = gtk_expander_buildable_add_child;
Johan Dahlin's avatar
Johan Dahlin committed
418 419
}

420 421
static void
gtk_expander_set_property (GObject      *object,
422 423 424
                           guint         prop_id,
                           const GValue *value,
                           GParamSpec   *pspec)
425 426
{
  GtkExpander *expander = GTK_EXPANDER (object);
427

428 429 430 431 432 433 434 435 436 437 438
  switch (prop_id)
    {
    case PROP_EXPANDED:
      gtk_expander_set_expanded (expander, g_value_get_boolean (value));
      break;
    case PROP_LABEL:
      gtk_expander_set_label (expander, g_value_get_string (value));
      break;
    case PROP_USE_UNDERLINE:
      gtk_expander_set_use_underline (expander, g_value_get_boolean (value));
      break;
439 440 441
    case PROP_USE_MARKUP:
      gtk_expander_set_use_markup (expander, g_value_get_boolean (value));
      break;
Soeren Sandmann's avatar
Soeren Sandmann committed
442
    case PROP_SPACING:
443 444 445 446 447
      gtk_expander_set_spacing (expander, g_value_get_int (value));
      break;
    case PROP_LABEL_WIDGET:
      gtk_expander_set_label_widget (expander, g_value_get_object (value));
      break;
448 449 450
    case PROP_LABEL_FILL:
      gtk_expander_set_label_fill (expander, g_value_get_boolean (value));
      break;
451 452 453
    case PROP_RESIZE_TOPLEVEL:
      gtk_expander_set_resize_toplevel (expander, g_value_get_boolean (value));
      break;
454 455 456 457 458 459 460 461
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_expander_get_property (GObject    *object,
462 463 464
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
{
  GtkExpander *expander = GTK_EXPANDER (object);
  GtkExpanderPrivate *priv = expander->priv;

  switch (prop_id)
    {
    case PROP_EXPANDED:
      g_value_set_boolean (value, priv->expanded);
      break;
    case PROP_LABEL:
      g_value_set_string (value, gtk_expander_get_label (expander));
      break;
    case PROP_USE_UNDERLINE:
      g_value_set_boolean (value, priv->use_underline);
      break;
480 481 482
    case PROP_USE_MARKUP:
      g_value_set_boolean (value, priv->use_markup);
      break;
Soeren Sandmann's avatar
Soeren Sandmann committed
483
    case PROP_SPACING:
484 485 486 487
      g_value_set_int (value, priv->spacing);
      break;
    case PROP_LABEL_WIDGET:
      g_value_set_object (value,
488 489
                          priv->label_widget ?
                          G_OBJECT (priv->label_widget) : NULL);
490
      break;
491 492 493
    case PROP_LABEL_FILL:
      g_value_set_boolean (value, priv->label_fill);
      break;
494 495 496
    case PROP_RESIZE_TOPLEVEL:
      g_value_set_boolean (value, gtk_expander_get_resize_toplevel (expander));
      break;
497 498 499 500 501 502
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

503
static void
504
gtk_expander_destroy (GtkWidget *widget)
505
{
506 507
  GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;

508
  if (priv->expand_timer)
509
    {
510 511
      g_source_remove (priv->expand_timer);
      priv->expand_timer = 0;
512
    }
513 514

  GTK_WIDGET_CLASS (gtk_expander_parent_class)->destroy (widget);
515 516
}

517 518 519
static void
gtk_expander_realize (GtkWidget *widget)
{
520
  GtkAllocation allocation;
521
  GtkExpanderPrivate *priv;
522
  GdkWindow *window;
523 524 525
  GdkWindowAttr attributes;
  gint attributes_mask;
  gint border_width;
526
  GdkRectangle expander_rect;
527
  gint label_height;
528 529

  priv = GTK_EXPANDER (widget)->priv;
530

531
  gtk_widget_set_realized (widget, TRUE);
532

533
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
534 535

  get_expander_bounds (GTK_EXPANDER (widget), &expander_rect);
536

537
  if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
538 539 540
    {
      GtkRequisition label_requisition;

541 542
      gtk_widget_get_preferred_size (priv->label_widget,
                                     &label_requisition, NULL);
543 544 545 546 547
      label_height = label_requisition.height;
    }
  else
    label_height = 0;

548 549
  gtk_widget_get_allocation (widget, &allocation);

550
  attributes.window_type = GDK_WINDOW_CHILD;
551 552 553
  attributes.x = allocation.x + border_width;
  attributes.y = allocation.y + border_width;
  attributes.width = MAX (allocation.width - 2 * border_width, 1);
554
  attributes.height = MAX (expander_rect.height, label_height - 2 * border_width);
555
  attributes.wclass = GDK_INPUT_ONLY;
556 557 558 559 560
  attributes.event_mask = gtk_widget_get_events (widget)
                          | GDK_BUTTON_PRESS_MASK
                          | GDK_BUTTON_RELEASE_MASK
                          | GDK_ENTER_NOTIFY_MASK
                          | GDK_LEAVE_NOTIFY_MASK;
561 562 563

  attributes_mask = GDK_WA_X | GDK_WA_Y;

564 565 566
  window = gtk_widget_get_parent_window (widget);
  gtk_widget_set_window (widget, window);
  g_object_ref (window);
567 568

  priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
569
                                       &attributes, attributes_mask);
570
  gtk_widget_register_window (widget, priv->event_window);
571 572 573 574 575 576 577 578 579
}

static void
gtk_expander_unrealize (GtkWidget *widget)
{
  GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;

  if (priv->event_window)
    {
580
      gtk_widget_unregister_window (widget, priv->event_window);
581 582 583 584
      gdk_window_destroy (priv->event_window);
      priv->event_window = NULL;
    }

Matthias Clasen's avatar
Matthias Clasen committed
585
  GTK_WIDGET_CLASS (gtk_expander_parent_class)->unrealize (widget);
586 587 588 589
}

static void
get_expander_bounds (GtkExpander  *expander,
590
                     GdkRectangle *rect)
591
{
592
  GtkAllocation allocation;
593 594 595 596 597 598 599 600 601 602 603 604 605
  GtkWidget *widget;
  GtkExpanderPrivate *priv;
  gint border_width;
  gint expander_size;
  gint expander_spacing;
  gboolean interior_focus;
  gint focus_width;
  gint focus_pad;
  gboolean ltr;

  widget = GTK_WIDGET (expander);
  priv = expander->priv;

606 607
  gtk_widget_get_allocation (widget, &allocation);

608
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
609 610

  gtk_widget_style_get (widget,
611 612 613 614 615 616
                        "interior-focus", &interior_focus,
                        "focus-line-width", &focus_width,
                        "focus-padding", &focus_pad,
                        "expander-size", &expander_size,
                        "expander-spacing", &expander_spacing,
                        NULL);
617 618 619

  ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;

620 621
  rect->x = allocation.x + border_width;
  rect->y = allocation.y + border_width;
622 623 624 625

  if (ltr)
    rect->x += expander_spacing;
  else
626
    rect->x += allocation.width - 2 * border_width -
627 628
               expander_spacing - expander_size;

629
  if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
630 631 632
    {
      GtkAllocation label_allocation;

633
      gtk_widget_get_allocation (priv->label_widget, &label_allocation);
634 635

      if (expander_size < label_allocation.height)
636
        rect->y += focus_width + focus_pad + (label_allocation.height - expander_size) / 2;
637
      else
638
        rect->y += expander_spacing;
639 640 641 642 643 644 645 646 647
    }
  else
    {
      rect->y += expander_spacing;
    }

  if (!interior_focus)
    {
      if (ltr)
648
        rect->x += focus_width + focus_pad;
649
      else
650
        rect->x -= focus_width + focus_pad;
651 652 653 654 655 656 657 658
      rect->y += focus_width + focus_pad;
    }

  rect->width = rect->height = expander_size;
}

static void
gtk_expander_size_allocate (GtkWidget     *widget,
659
                            GtkAllocation *allocation)
660 661
{
  GtkExpander *expander;
Javier Jardón's avatar
Javier Jardón committed
662
  GtkWidget *child;
663
  GtkExpanderPrivate *priv;
664
  gboolean child_visible = FALSE;
665
  guint border_width;
666 667 668 669 670
  gint expander_size;
  gint expander_spacing;
  gboolean interior_focus;
  gint focus_width;
  gint focus_pad;
671 672 673
  gint label_height, top_min_height;
  gint label_xpad, label_xoffset;
  gint child_ypad, child_yoffset;
674 675

  expander = GTK_EXPANDER (widget);
676 677
  child    = gtk_bin_get_child (GTK_BIN (widget));
  priv     = expander->priv;
678

679
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
680

681
  gtk_widget_set_allocation (widget, allocation);
682

683
  gtk_widget_style_get (widget,
684 685 686 687 688 689
                        "interior-focus", &interior_focus,
                        "focus-line-width", &focus_width,
                        "focus-padding", &focus_pad,
                        "expander-size", &expander_size,
                        "expander-spacing", &expander_spacing,
                        NULL);
690

Javier Jardón's avatar
Javier Jardón committed
691

692
  /* Calculate some offsets/padding first */
693 694
  label_xoffset = border_width + expander_size + focus_width + 2 * expander_spacing + focus_pad;
  label_xpad = 2 * border_width + expander_size + 2 * focus_width + 2 * expander_spacing + 2 * focus_pad;
695

696 697 698 699
  child_yoffset  = border_width + priv->spacing + (interior_focus ? 0 : 2 * focus_width + 2 * focus_pad);
  child_ypad     = 2 * border_width + priv->spacing + (interior_focus ? 0 : 2 * focus_width + 2 * focus_pad);
  top_min_height = 2 * expander_spacing + expander_size;

700
  child_visible = (child && gtk_widget_get_child_visible (child));
701

702
  if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
703 704
    {
      GtkAllocation label_allocation;
705
      gint          natural_label_width;
706 707
      gboolean ltr;

708
      gtk_widget_get_preferred_width (priv->label_widget, NULL, &natural_label_width);
709

710 711 712 713
      if (priv->label_fill)
        label_allocation.width = allocation->width - label_xpad;
      else
        label_allocation.width = MIN (natural_label_width, allocation->width - label_xpad);
714 715 716
      label_allocation.width = MAX (label_allocation.width, 1);

      /* We distribute the minimum height to the label widget and prioritize
717 718
       * the child widget giving it the remaining height
       */
719 720
      gtk_widget_get_preferred_height_for_width (priv->label_widget,
                                                 label_allocation.width, &label_height, NULL);
721 722 723

      ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;

724
      if (priv->label_fill)
725
        label_allocation.x = allocation->x + label_xoffset;
726
      else if (ltr)
727
        label_allocation.x = allocation->x + label_xoffset;
728
      else
729 730
        label_allocation.x = allocation->x + allocation->width -
                             (label_allocation.width + label_xoffset);
731

732
      label_allocation.y = allocation->y + border_width + focus_width + focus_pad;
733
      label_allocation.height = MIN (label_height,
734 735 736
                                     allocation->height - 2 * border_width -
                                     2 * focus_width - 2 * focus_pad -
                                     (child_visible ? priv->spacing : 0));
737 738 739 740 741 742 743 744 745 746 747
      label_allocation.height = MAX (label_allocation.height, 1);

      gtk_widget_size_allocate (priv->label_widget, &label_allocation);

      label_height = label_allocation.height;
    }
  else
    {
      label_height = 0;
    }

748
  if (gtk_widget_get_realized (widget))
749 750 751 752 753 754
    {
      GdkRectangle rect;

      get_expander_bounds (expander, &rect);

      gdk_window_move_resize (priv->event_window,
755 756 757 758
                              allocation->x + border_width,
                              allocation->y + border_width,
                              MAX (allocation->width - 2 * border_width, 1),
                              MAX (rect.height, label_height - 2 * border_width));
759 760 761 762 763 764 765
    }

  if (child_visible)
    {
      GtkAllocation child_allocation;
      gint top_height;

766
      top_height = MAX (top_min_height,
767
                        label_height + (interior_focus ? 2 * focus_width + 2 * focus_pad : 0));
768

769 770
      child_allocation.x = allocation->x + border_width;
      child_allocation.y = allocation->y + top_height + child_yoffset;
771 772

      child_allocation.width = MAX (allocation->width - 2 * border_width, 1);
773
      child_allocation.height = allocation->height - top_height - child_ypad;
774 775
      child_allocation.height = MAX (child_allocation.height, 1);

Javier Jardón's avatar
Javier Jardón committed
776
      gtk_widget_size_allocate (child, &child_allocation);
777 778 779 780 781 782 783 784 785 786 787
    }
}

static void
gtk_expander_map (GtkWidget *widget)
{
  GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;

  if (priv->label_widget)
    gtk_widget_map (priv->label_widget);

Matthias Clasen's avatar
Matthias Clasen committed
788
  GTK_WIDGET_CLASS (gtk_expander_parent_class)->map (widget);
789 790 791 792 793 794 795 796 797 798 799 800 801

  if (priv->event_window)
    gdk_window_show (priv->event_window);
}

static void
gtk_expander_unmap (GtkWidget *widget)
{
  GtkExpanderPrivate *priv = GTK_EXPANDER (widget)->priv;

  if (priv->event_window)
    gdk_window_hide (priv->event_window);

Matthias Clasen's avatar
Matthias Clasen committed
802
  GTK_WIDGET_CLASS (gtk_expander_parent_class)->unmap (widget);
803 804 805 806 807

  if (priv->label_widget)
    gtk_widget_unmap (priv->label_widget);
}

808
static void
809 810
gtk_expander_paint_prelight (GtkExpander *expander,
                             cairo_t     *cr)
811
{
812
  GtkAllocation allocation;
813 814 815 816
  GtkWidget *widget;
  GtkContainer *container;
  GtkExpanderPrivate *priv;
  GdkRectangle area;
817
  GtkStyleContext *context;
818 819 820 821 822
  gboolean interior_focus;
  int focus_width;
  int focus_pad;
  int expander_size;
  int expander_spacing;
823
  guint border_width;
824 825 826 827 828 829

  priv = expander->priv;
  widget = GTK_WIDGET (expander);
  container = GTK_CONTAINER (expander);

  gtk_widget_style_get (widget,
830 831 832 833 834 835
                        "interior-focus", &interior_focus,
                        "focus-line-width", &focus_width,
                        "focus-padding", &focus_pad,
                        "expander-size", &expander_size,
                        "expander-spacing", &expander_spacing,
                        NULL);
836

837 838
  gtk_widget_get_allocation (widget, &allocation);

839
  border_width = gtk_container_get_border_width (container);
Benjamin Otte's avatar
Benjamin Otte committed
840 841
  area.x = border_width;
  area.y = border_width;
842
  area.width = allocation.width - (2 * border_width);
843

844
  if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
845 846 847 848 849 850
    {
      GtkAllocation label_widget_allocation;

      gtk_widget_get_allocation (priv->label_widget, &label_widget_allocation);
      area.height = label_widget_allocation.height;
    }
851 852 853 854 855 856 857
  else
    area.height = 0;

  area.height += interior_focus ? (focus_width + focus_pad) * 2 : 0;
  area.height = MAX (area.height, expander_size + 2 * expander_spacing);
  area.height += !interior_focus ? (focus_width + focus_pad) * 2 : 0;

858 859 860 861
  context = gtk_widget_get_style_context (widget);
  gtk_render_background (context, cr,
                         area.x, area.y,
                         area.width, area.height);
862 863
}

864
static void
865 866
gtk_expander_paint (GtkExpander *expander,
                    cairo_t     *cr)
867
{
868
  GtkExpanderPrivate *priv = expander->priv;
869 870
  GtkWidget *widget;
  GdkRectangle clip;
Benjamin Otte's avatar
Benjamin Otte committed
871
  GtkAllocation allocation;
872 873 874
  GtkStyleContext *context;
  GtkStateFlags state = 0;
  gint size;
875 876

  widget = GTK_WIDGET (expander);
877
  context = gtk_widget_get_style_context (widget);
878
  state = gtk_widget_get_state_flags (widget);
879 880

  get_expander_bounds (expander, &clip);
Benjamin Otte's avatar
Benjamin Otte committed
881
  gtk_widget_get_allocation (widget, &allocation);
882

883 884
  gtk_style_context_save (context);

885
  state &= ~(GTK_STATE_FLAG_PRELIGHT);
886
  if (expander->priv->prelight)
887
    {
888
      state |= GTK_STATE_FLAG_PRELIGHT;
889
      gtk_style_context_set_state (context, state);
Benjamin Otte's avatar
Benjamin Otte committed
890
      gtk_expander_paint_prelight (expander, cr);
891
    }
892

893 894 895 896 897
  gtk_widget_style_get (widget, "expander-size", &size, NULL);

  /* Set active flag as per the expanded state */
  if (priv->expanded)
    state |= GTK_STATE_FLAG_ACTIVE;
898 899
  else
    state &= ~(GTK_STATE_FLAG_ACTIVE);
900 901 902 903 904 905 906 907 908 909

  gtk_style_context_set_state (context, state);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_EXPANDER);

  gtk_render_expander (context, cr,
                       clip.x - allocation.x,
                       clip.y - allocation.y,
                       size, size);

  gtk_style_context_restore (context);
910 911 912
}

static void
Benjamin Otte's avatar
Benjamin Otte committed
913
gtk_expander_paint_focus (GtkExpander *expander,
914
                          cairo_t     *cr)
915 916 917
{
  GtkWidget *widget;
  GtkExpanderPrivate *priv;
918
  GdkRectangle rect;
919
  GtkStyleContext *context;
920 921 922 923 924 925 926 927
  gint x, y, width, height;
  gboolean interior_focus;
  gint border_width;
  gint focus_width;
  gint focus_pad;
  gint expander_size;
  gint expander_spacing;
  gboolean ltr;
Benjamin Otte's avatar
Benjamin Otte committed
928
  GtkAllocation allocation;
929 930 931 932

  widget = GTK_WIDGET (expander);
  priv = expander->priv;

933
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
Benjamin Otte's avatar
Benjamin Otte committed
934
  gtk_widget_get_allocation (widget, &allocation);
935 936

  gtk_widget_style_get (widget,
937 938 939 940 941 942
                        "interior-focus", &interior_focus,
                        "focus-line-width", &focus_width,
                        "focus-padding", &focus_pad,
                        "expander-size", &expander_size,
                        "expander-spacing", &expander_spacing,
                        NULL);
943 944

  ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
945

946 947
  width = height = 0;

948
  if (priv->label_widget)
949
    {
950
      if (gtk_widget_get_visible (priv->label_widget))
951 952
        {
          GtkAllocation label_allocation;
953

954 955 956 957
          gtk_widget_get_allocation (priv->label_widget, &label_allocation);
          width  = label_allocation.width;
          height = label_allocation.height;
        }
958

959 960
      width  += 2 * focus_pad + 2 * focus_width;
      height += 2 * focus_pad + 2 * focus_width;
961

Benjamin Otte's avatar
Benjamin Otte committed
962 963
      x = border_width;
      y = border_width;
964

965
      if (ltr)
966 967 968 969
        {
          if (interior_focus)
            x += expander_spacing * 2 + expander_size;
        }
970
      else
971 972 973 974
        {
          x += allocation.width - 2 * border_width
            - expander_spacing * 2 - expander_size - width;
        }
975 976

      if (!interior_focus)
977 978 979 980
        {
          width += expander_size + 2 * expander_spacing;
          height = MAX (height, expander_size + 2 * expander_spacing);
        }
981 982 983
    }
  else
    {
984
      get_expander_bounds (expander, &rect);
985

Benjamin Otte's avatar
Benjamin Otte committed
986 987
      x = rect.x - allocation.x - focus_pad;
      y = rect.y - allocation.y - focus_pad;
988 989
      width = rect.width + 2 * focus_pad;
      height = rect.height + 2 * focus_pad;
990
    }
991

992 993 994
  context = gtk_widget_get_style_context (widget);
  gtk_render_focus (context, cr,
                    x, y, width, height);
995 996 997
}

static gboolean
Benjamin Otte's avatar
Benjamin Otte committed
998
gtk_expander_draw (GtkWidget *widget,
999
                   cairo_t   *cr)
1000
{
Benjamin Otte's avatar
Benjamin Otte committed
1001
  GtkExpander *expander = GTK_EXPANDER (widget);
1002

Benjamin Otte's avatar
Benjamin Otte committed
1003
  gtk_expander_paint (expander, cr);
1004

1005
  if (gtk_widget_has_visible_focus (widget))
Benjamin Otte's avatar
Benjamin Otte committed
1006
    gtk_expander_paint_focus (expander, cr);
1007

Benjamin Otte's avatar
Benjamin Otte committed
1008
  GTK_WIDGET_CLASS (gtk_expander_parent_class)->draw (widget, cr);
1009 1010 1011 1012 1013 1014

  return FALSE;
}

static gboolean
gtk_expander_button_press (GtkWidget      *widget,
1015
                           GdkEventButton *event)
1016 1017 1018
{
  GtkExpander *expander = GTK_EXPANDER (widget);

1019
  if (event->button == GDK_BUTTON_PRIMARY && event->window == expander->priv->event_window)
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
    {
      expander->priv->button_down = TRUE;
      return TRUE;
    }

  return FALSE;
}

static gboolean
gtk_expander_button_release (GtkWidget      *widget,
1030
                             GdkEventButton *event)
1031 1032 1033
{
  GtkExpander *expander = GTK_EXPANDER (widget);

1034
  if (event->button == GDK_BUTTON_PRIMARY && expander->priv->button_down)
1035
    {
1036 1037
      if (expander->priv->prelight)
        gtk_widget_activate (widget);
1038 1039 1040 1041 1042 1043 1044 1045 1046
      expander->priv->button_down = FALSE;
      return TRUE;
    }

  return FALSE;
}

static void
gtk_expander_grab_notify (GtkWidget *widget,
1047
                          gboolean   was_grabbed)
1048 1049 1050 1051 1052 1053
{
  if (!was_grabbed)
    GTK_EXPANDER (widget)->priv->button_down = FALSE;
}

static void
1054 1055
gtk_expander_state_flags_changed (GtkWidget    *widget,
                                  GtkStateFlags  previous_state)
1056
{
1057
  if (!gtk_widget_is_sensitive (widget))
1058 1059 1060 1061 1062 1063
    GTK_EXPANDER (widget)->priv->button_down = FALSE;
}

static void
gtk_expander_redraw_expander (GtkExpander *expander)
{
1064 1065
  GtkAllocation allocation;
  GtkWidget *widget = GTK_WIDGET (expander);
1066

1067
  if (gtk_widget_get_realized (widget))
1068 1069 1070 1071
    {
      gtk_widget_get_allocation (widget, &allocation);
      gdk_window_invalidate_rect (gtk_widget_get_window (widget), &allocation, FALSE);
    }
1072 1073 1074 1075
}

static gboolean
gtk_expander_enter_notify (GtkWidget        *widget,
1076
                           GdkEventCrossing *event)
1077 1078 1079
{
  GtkExpander *expander = GTK_EXPANDER (widget);

1080
  if (event->window == expander->priv->event_window &&
1081 1082 1083
      event->detail != GDK_NOTIFY_INFERIOR)
    {
      expander->priv->prelight = TRUE;
1084 1085

      if (expander->priv->label_widget)
1086 1087 1088
        gtk_widget_set_state_flags (expander->priv->label_widget,
                                    GTK_STATE_FLAG_PRELIGHT,
                                    FALSE);
1089

1090 1091 1092 1093 1094 1095 1096 1097
      gtk_expander_redraw_expander (expander);
    }

  return FALSE;
}

static gboolean
gtk_expander_leave_notify (GtkWidget        *widget,
1098
                           GdkEventCrossing *event)
1099 1100 1101
{
  GtkExpander *expander = GTK_EXPANDER (widget);

1102
  if (event->window == expander->priv->event_window &&
1103 1104 1105
      event->detail != GDK_NOTIFY_INFERIOR)
    {
      expander->priv->prelight = FALSE;
1106 1107

      if (expander->priv->label_widget)
1108 1109
        gtk_widget_unset_state_flags (expander->priv->label_widget,
                                      GTK_STATE_FLAG_PRELIGHT);
1110

1111 1112 1113 1114 1115 1116
      gtk_expander_redraw_expander (expander);
    }

  return FALSE;
}

1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
static gboolean
expand_timeout (gpointer data)
{
  GtkExpander *expander = GTK_EXPANDER (data);
  GtkExpanderPrivate *priv = expander->priv;

  priv->expand_timer = 0;
  gtk_expander_set_expanded (expander, TRUE);

  return FALSE;
}

static gboolean
gtk_expander_drag_motion (GtkWidget        *widget,
1131 1132 1133 1134
                          GdkDragContext   *context,
                          gint              x,
                          gint              y,
                          guint             time)
1135 1136 1137 1138 1139 1140
{
  GtkExpander *expander = GTK_EXPANDER (widget);
  GtkExpanderPrivate *priv = expander->priv;

  if (!priv->expanded && !priv->expand_timer)
    {
1141
      priv->expand_timer = gdk_threads_add_timeout (TIMEOUT_EXPAND, (GSourceFunc) expand_timeout, expander);
Bastien Nocera's avatar
Bastien Nocera committed
1142
      g_source_set_name_by_id (priv->expand_timer, "[gtk+] expand_timeout");
1143 1144 1145 1146 1147 1148 1149
    }

  return TRUE;
}

static void
gtk_expander_drag_leave (GtkWidget      *widget,
1150 1151
                         GdkDragContext *context,
                         guint           time)
1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
{
  GtkExpander *expander = GTK_EXPANDER (widget);
  GtkExpanderPrivate *priv = expander->priv;

  if (priv->expand_timer)
    {
      g_source_remove (priv->expand_timer);
      priv->expand_timer = 0;
    }
}

1163 1164 1165 1166 1167 1168 1169