gtkeventbox.c 19.1 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6 7 8 9 10 11
 * 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
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
17 18

/*
19
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20 21
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
22
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 24
 */

25
#include "config.h"
26

Elliot Lee's avatar
Elliot Lee committed
27
#include "gtkeventbox.h"
28

29
#include "gtkintl.h"
30 31 32
#include "gtkprivate.h"
#include "gtkrender.h"
#include "gtksizerequest.h"
33

Elliot Lee's avatar
Elliot Lee committed
34

35 36 37 38 39 40 41 42 43 44 45 46
/**
 * SECTION:gtkeventbox
 * @Short_description: A widget used to catch events for widgets which
 *     do not have their own window
 * @Title: GtkEventBox
 *
 * The #GtkEventBox widget is a subclass of #GtkBin which also has its
 * own window. It is useful since it allows you to catch events for widgets
 * which do not have their own window.
 */

struct _GtkEventBoxPrivate
47 48 49
{
  gboolean above_child;
  GdkWindow *event_window;
50
};
51 52 53 54 55 56 57

enum {
  PROP_0,
  PROP_VISIBLE_WINDOW,
  PROP_ABOVE_CHILD
};

58 59 60 61
static void     gtk_event_box_realize       (GtkWidget        *widget);
static void     gtk_event_box_unrealize     (GtkWidget        *widget);
static void     gtk_event_box_map           (GtkWidget        *widget);
static void     gtk_event_box_unmap         (GtkWidget        *widget);
62 63 64 65 66 67
static void     gtk_event_box_get_preferred_width  (GtkWidget *widget,
                                                    gint      *minimum,
                                                    gint      *natural);
static void     gtk_event_box_get_preferred_height (GtkWidget *widget,
                                                    gint      *minimum,
                                                    gint      *natural);
68 69 70 71 72 73
static void     gtk_event_box_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
									   gint       width,
									   gint      *minimum,
									   gint      *natural,
									   gint      *minimum_baseline,
									   gint      *natural_baseline);
74 75
static void     gtk_event_box_size_allocate (GtkWidget        *widget,
                                             GtkAllocation    *allocation);
76 77
static gboolean gtk_event_box_draw          (GtkWidget        *widget,
                                             cairo_t          *cr);
78 79 80 81 82 83 84 85
static void     gtk_event_box_set_property  (GObject          *object,
                                             guint             prop_id,
                                             const GValue     *value,
                                             GParamSpec       *pspec);
static void     gtk_event_box_get_property  (GObject          *object,
                                             guint             prop_id,
                                             GValue           *value,
                                             GParamSpec       *pspec);
Elliot Lee's avatar
Elliot Lee committed
86

87
G_DEFINE_TYPE_WITH_PRIVATE (GtkEventBox, gtk_event_box, GTK_TYPE_BIN)
Elliot Lee's avatar
Elliot Lee committed
88 89 90 91

static void
gtk_event_box_class_init (GtkEventBoxClass *class)
{
92 93
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
94
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
95

96 97
  gobject_class->set_property = gtk_event_box_set_property;
  gobject_class->get_property = gtk_event_box_get_property;
98

Elliot Lee's avatar
Elliot Lee committed
99
  widget_class->realize = gtk_event_box_realize;
100 101 102
  widget_class->unrealize = gtk_event_box_unrealize;
  widget_class->map = gtk_event_box_map;
  widget_class->unmap = gtk_event_box_unmap;
103 104
  widget_class->get_preferred_width = gtk_event_box_get_preferred_width;
  widget_class->get_preferred_height = gtk_event_box_get_preferred_height;
105
  widget_class->get_preferred_height_and_baseline_for_width = gtk_event_box_get_preferred_height_and_baseline_for_width;
Elliot Lee's avatar
Elliot Lee committed
106
  widget_class->size_allocate = gtk_event_box_size_allocate;
107
  widget_class->draw = gtk_event_box_draw;
108

109 110
  gtk_container_class_handle_border_width (container_class);

111 112 113
  g_object_class_install_property (gobject_class,
                                   PROP_VISIBLE_WINDOW,
                                   g_param_spec_boolean ("visible-window",
114 115
                                                        P_("Visible Window"),
                                                        P_("Whether the event box is visible, as opposed to invisible and only used to trap events."),
116
                                                        TRUE,
117
                                                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
118 119 120
  g_object_class_install_property (gobject_class,
                                   PROP_ABOVE_CHILD,
                                   g_param_spec_boolean ("above-child",
121 122
                                                        P_("Above child"),
                                                        P_("Whether the event-trapping window of the eventbox is above the window of the child widget as opposed to below it."),
123
                                                        FALSE,
124
                                                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
Elliot Lee's avatar
Elliot Lee committed
125 126 127 128 129
}

static void
gtk_event_box_init (GtkEventBox *event_box)
{
130
  gtk_widget_set_has_window (GTK_WIDGET (event_box), TRUE);
131

132 133
  event_box->priv = gtk_event_box_get_instance_private (event_box);
  event_box->priv->above_child = FALSE;
Elliot Lee's avatar
Elliot Lee committed
134 135
}

136 137 138 139 140 141 142
/**
 * gtk_event_box_new:
 *
 * Creates a new #GtkEventBox.
 *
 * Returns: a new #GtkEventBox
 */
Elliot Lee's avatar
Elliot Lee committed
143
GtkWidget*
144
gtk_event_box_new (void)
Elliot Lee's avatar
Elliot Lee committed
145
{
Manish Singh's avatar
Manish Singh committed
146
  return g_object_new (GTK_TYPE_EVENT_BOX, NULL);
Elliot Lee's avatar
Elliot Lee committed
147 148
}

149
static void
150
gtk_event_box_set_property (GObject      *object,
151 152 153
                            guint         prop_id,
                            const GValue *value,
                            GParamSpec   *pspec)
154 155
{
  GtkEventBox *event_box;
156

157
  event_box = GTK_EVENT_BOX (object);
158

159 160 161 162
  switch (prop_id)
    {
    case PROP_VISIBLE_WINDOW:
      gtk_event_box_set_visible_window (event_box, g_value_get_boolean (value));
163 164
      break;

165 166
    case PROP_ABOVE_CHILD:
      gtk_event_box_set_above_child (event_box, g_value_get_boolean (value));
167 168
      break;

169 170 171 172 173 174
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

175
static void
176
gtk_event_box_get_property (GObject     *object,
177 178 179
                            guint        prop_id,
                            GValue      *value,
                            GParamSpec  *pspec)
180 181
{
  GtkEventBox *event_box;
182

183
  event_box = GTK_EVENT_BOX (object);
184

185 186 187 188 189
  switch (prop_id)
    {
    case PROP_VISIBLE_WINDOW:
      g_value_set_boolean (value, gtk_event_box_get_visible_window (event_box));
      break;
190

191 192 193
    case PROP_ABOVE_CHILD:
      g_value_set_boolean (value, gtk_event_box_get_above_child (event_box));
      break;
194

195 196 197 198 199 200 201 202 203 204 205 206 207
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

/**
 * gtk_event_box_get_visible_window:
 * @event_box: a #GtkEventBox
 *
 * Returns whether the event box has a visible window.
 * See gtk_event_box_set_visible_window() for details.
 *
208
 * Returns: %TRUE if the event box window is visible
209 210
 *
 * Since: 2.4
211
 */
212 213 214 215 216
gboolean
gtk_event_box_get_visible_window (GtkEventBox *event_box)
{
  g_return_val_if_fail (GTK_IS_EVENT_BOX (event_box), FALSE);

217
  return gtk_widget_get_has_window (GTK_WIDGET (event_box));
218 219 220 221 222
}

/**
 * gtk_event_box_set_visible_window:
 * @event_box: a #GtkEventBox
223
 * @visible_window: %TRUE to make the event box have a visible window
224 225 226 227
 *
 * Set whether the event box uses a visible or invisible child
 * window. The default is to use visible windows.
 *
Matthias Clasen's avatar
Matthias Clasen committed
228
 * In an invisible window event box, the window that the
229
 * event box creates is a %GDK_INPUT_ONLY window, which
230 231
 * means that it is invisible and only serves to receive
 * events.
232
 *
233
 * A visible window event box creates a visible (%GDK_INPUT_OUTPUT)
234
 * window that acts as the parent window for all the widgets
235
 * contained in the event box.
236
 *
237 238 239 240
 * You should generally make your event box invisible if
 * you just want to trap events. Creating a visible window
 * may cause artifacts that are visible to the user, especially
 * if the user is using a theme with gradients or pixmaps.
241
 *
242 243 244 245 246 247 248
 * The main reason to create a non input-only event box is if
 * you want to set the background to a different color or
 * draw on it.
 *
 * There is one unexpected issue for an invisible event box that has its
 * window below the child. (See gtk_event_box_set_above_child().)
 * Since the input-only window is not an ancestor window of any windows
249
 * that descendent widgets of the event box create, events on these
250 251
 * windows aren’t propagated up by the windowing system, but only by GTK+.
 * The practical effect of this is if an event isn’t in the event
252
 * mask for the descendant window (see gtk_widget_add_events()),
253
 * it won’t be received by the event box.
254
 * 
255
 * This problem doesn’t occur for visible event boxes, because in
256 257
 * that case, the event box window is actually the ancestor of the
 * descendant windows, not just at the same place on the screen.
258
 *
259
 * Since: 2.4
260
 */
261 262
void
gtk_event_box_set_visible_window (GtkEventBox *event_box,
263
                                  gboolean     visible_window)
264 265 266 267 268 269 270 271 272
{
  GtkWidget *widget;

  g_return_if_fail (GTK_IS_EVENT_BOX (event_box));

  widget = GTK_WIDGET (event_box);

  visible_window = visible_window != FALSE;

273
  if (visible_window != gtk_widget_get_has_window (widget))
274
    {
275
      if (gtk_widget_get_realized (widget))
276 277
        {
          gboolean visible = gtk_widget_get_visible (widget);
278

279 280
          if (visible)
            gtk_widget_hide (widget);
281

282
          gtk_widget_unrealize (widget);
283

284 285
          gtk_widget_set_has_window (widget, visible_window);

286
          gtk_widget_realize (widget);
287

288 289 290
          if (visible)
            gtk_widget_show (widget);
        }
291
      else
292
        {
293
          gtk_widget_set_has_window (widget, visible_window);
294
        }
295

296
      if (gtk_widget_get_visible (widget))
297 298
        gtk_widget_queue_resize (widget);

299 300 301 302 303 304 305 306 307
      g_object_notify (G_OBJECT (event_box), "visible-window");
    }
}

/**
 * gtk_event_box_get_above_child:
 * @event_box: a #GtkEventBox
 *
 * Returns whether the event box window is above or below the
308 309
 * windows of its child. See gtk_event_box_set_above_child()
 * for details.
310
 *
311
 * Returns: %TRUE if the event box window is above the
312
 *     window of its child
313 314
 *
 * Since: 2.4
315
 */
316 317 318
gboolean
gtk_event_box_get_above_child (GtkEventBox *event_box)
{
319
  GtkEventBoxPrivate *priv = event_box->priv;
320 321 322 323 324 325 326 327 328

  g_return_val_if_fail (GTK_IS_EVENT_BOX (event_box), FALSE);

  return priv->above_child;
}

/**
 * gtk_event_box_set_above_child:
 * @event_box: a #GtkEventBox
329
 * @above_child: %TRUE if the event box window is above its child
330
 *
331 332 333 334 335
 * Set whether the event box window is positioned above the windows
 * of its child, as opposed to below it. If the window is above, all
 * events inside the event box will go to the event box. If the window
 * is below, events in windows of child widgets will first got to that
 * widget, and then to its parents.
336 337
 *
 * The default is to keep the window below the child.
338
 *
339
 * Since: 2.4
340
 */
341 342
void
gtk_event_box_set_above_child (GtkEventBox *event_box,
343
                               gboolean     above_child)
344
{
345
  GtkEventBoxPrivate *priv = event_box->priv;
346 347 348 349 350 351 352 353 354 355
  GtkWidget *widget;

  g_return_if_fail (GTK_IS_EVENT_BOX (event_box));

  widget = GTK_WIDGET (event_box);

  above_child = above_child != FALSE;

  if (priv->above_child != above_child)
    {
356 357
      priv->above_child = above_child;

358
      if (gtk_widget_get_realized (widget))
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
        {
          if (!gtk_widget_get_has_window (widget))
            {
              if (above_child)
                gdk_window_raise (priv->event_window);
              else
                gdk_window_lower (priv->event_window);
            }
          else
            {
              gboolean visible = gtk_widget_get_visible (widget);

              if (visible)
                gtk_widget_hide (widget);

              gtk_widget_unrealize (widget);
              gtk_widget_realize (widget);

              if (visible)
                gtk_widget_show (widget);
            }
        }
381

382
      if (gtk_widget_get_visible (widget))
383 384
        gtk_widget_queue_resize (widget);

385 386 387 388
      g_object_notify (G_OBJECT (event_box), "above-child");
    }
}

Elliot Lee's avatar
Elliot Lee committed
389 390 391
static void
gtk_event_box_realize (GtkWidget *widget)
{
392
  GtkEventBoxPrivate *priv;
393 394
  GtkAllocation allocation;
  GdkWindow *window;
Elliot Lee's avatar
Elliot Lee committed
395 396
  GdkWindowAttr attributes;
  gint attributes_mask;
397
  gboolean visible_window;
Elliot Lee's avatar
Elliot Lee committed
398

399 400
  gtk_widget_get_allocation (widget, &allocation);

401
  gtk_widget_set_realized (widget, TRUE);
Elliot Lee's avatar
Elliot Lee committed
402

403 404 405 406
  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
Elliot Lee's avatar
Elliot Lee committed
407 408
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.event_mask = gtk_widget_get_events (widget)
409 410 411 412 413 414
                        | GDK_BUTTON_MOTION_MASK
                        | GDK_BUTTON_PRESS_MASK
                        | GDK_BUTTON_RELEASE_MASK
                        | GDK_EXPOSURE_MASK
                        | GDK_ENTER_NOTIFY_MASK
                        | GDK_LEAVE_NOTIFY_MASK;
Elliot Lee's avatar
Elliot Lee committed
415

416
  priv = GTK_EVENT_BOX (widget)->priv;
417

418
  visible_window = gtk_widget_get_has_window (widget);
419 420 421 422
  if (visible_window)
    {
      attributes.visual = gtk_widget_get_visual (widget);
      attributes.wclass = GDK_INPUT_OUTPUT;
423

424
      attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
425 426 427 428

      window = gdk_window_new (gtk_widget_get_parent_window (widget),
                               &attributes, attributes_mask);
      gtk_widget_set_window (widget, window);
429
      gtk_widget_register_window (widget, window);
430 431 432
    }
  else
    {
433 434 435
      window = gtk_widget_get_parent_window (widget);
      gtk_widget_set_window (widget, window);
      g_object_ref (window);
436 437 438 439 440
    }

  if (!visible_window || priv->above_child)
    {
      attributes.wclass = GDK_INPUT_ONLY;
441 442 443 444
      if (!visible_window)
        attributes_mask = GDK_WA_X | GDK_WA_Y;
      else
        attributes_mask = 0;
445

446
      priv->event_window = gdk_window_new (window,
447
                                           &attributes, attributes_mask);
448
      gtk_widget_register_window (widget, priv->event_window);
449 450 451 452 453 454
    }
}

static void
gtk_event_box_unrealize (GtkWidget *widget)
{
455 456
  GtkEventBoxPrivate *priv = GTK_EVENT_BOX (widget)->priv;

457 458
  if (priv->event_window != NULL)
    {
459
      gtk_widget_unregister_window (widget, priv->event_window);
460 461 462 463
      gdk_window_destroy (priv->event_window);
      priv->event_window = NULL;
    }

464
  GTK_WIDGET_CLASS (gtk_event_box_parent_class)->unrealize (widget);
Elliot Lee's avatar
Elliot Lee committed
465 466
}

467 468 469
static void
gtk_event_box_map (GtkWidget *widget)
{
470
  GtkEventBoxPrivate *priv = GTK_EVENT_BOX (widget)->priv;
471 472 473

  if (priv->event_window != NULL && !priv->above_child)
    gdk_window_show (priv->event_window);
474 475

  GTK_WIDGET_CLASS (gtk_event_box_parent_class)->map (widget);
476 477 478 479 480 481 482 483

  if (priv->event_window != NULL && priv->above_child)
    gdk_window_show (priv->event_window);
}

static void
gtk_event_box_unmap (GtkWidget *widget)
{
484
  GtkEventBoxPrivate *priv = GTK_EVENT_BOX (widget)->priv;
485 486 487

  if (priv->event_window != NULL)
    gdk_window_hide (priv->event_window);
488 489

  GTK_WIDGET_CLASS (gtk_event_box_parent_class)->unmap (widget);
490 491
}

Elliot Lee's avatar
Elliot Lee committed
492
static void
493 494 495
gtk_event_box_get_preferred_width (GtkWidget *widget,
                                   gint      *minimum,
                                   gint      *natural)
Elliot Lee's avatar
Elliot Lee committed
496
{
497
  GtkBin *bin = GTK_BIN (widget);
Javier Jardón's avatar
Javier Jardón committed
498
  GtkWidget *child;
Elliot Lee's avatar
Elliot Lee committed
499

500 501
  *minimum = 0;
  *natural = 0;
Elliot Lee's avatar
Elliot Lee committed
502

Javier Jardón's avatar
Javier Jardón committed
503 504
  child = gtk_bin_get_child (bin);
  if (child && gtk_widget_get_visible (child))
505 506 507 508
    gtk_widget_get_preferred_width (child, minimum, natural);
}

static void
509 510 511 512 513 514
gtk_event_box_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
							   gint       width,
							   gint      *minimum,
							   gint      *natural,
							   gint      *minimum_baseline,
							   gint      *natural_baseline)
515 516 517
{
  GtkBin *bin = GTK_BIN (widget);
  GtkWidget *child;
518

519 520
  *minimum = 0;
  *natural = 0;
521

522 523 524 525 526 527
  if (minimum_baseline)
    *minimum_baseline = -1;

  if (natural_baseline)
    *natural_baseline = -1;

528 529
  child = gtk_bin_get_child (bin);
  if (child && gtk_widget_get_visible (child))
530
    gtk_widget_get_preferred_height_and_baseline_for_width (child,
531
							    width,
532 533 534 535 536 537 538 539 540 541 542 543 544 545
							    minimum,
							    natural,
							    minimum_baseline,
							    natural_baseline);
}

static void
gtk_event_box_get_preferred_height (GtkWidget *widget,
                                    gint      *minimum,
                                    gint      *natural)
{
  gtk_event_box_get_preferred_height_and_baseline_for_width (widget, -1,
							     minimum, natural,
							     NULL, NULL);
Elliot Lee's avatar
Elliot Lee committed
546 547 548 549
}

static void
gtk_event_box_size_allocate (GtkWidget     *widget,
550
                             GtkAllocation *allocation)
Elliot Lee's avatar
Elliot Lee committed
551 552 553
{
  GtkBin *bin;
  GtkAllocation child_allocation;
554
  gint baseline;
555
  GtkEventBoxPrivate *priv;
Javier Jardón's avatar
Javier Jardón committed
556 557
  GtkWidget *child;

Elliot Lee's avatar
Elliot Lee committed
558
  bin = GTK_BIN (widget);
559 560 561

  gtk_widget_set_allocation (widget, allocation);

562
  if (!gtk_widget_get_has_window (widget))
563
    {
564 565
      child_allocation.x = allocation->x;
      child_allocation.y = allocation->y;
566 567 568 569 570 571
    }
  else
    {
      child_allocation.x = 0;
      child_allocation.y = 0;
    }
572 573
  child_allocation.width = allocation->width;
  child_allocation.height = allocation->height;
Elliot Lee's avatar
Elliot Lee committed
574

575
  if (gtk_widget_get_realized (widget))
Elliot Lee's avatar
Elliot Lee committed
576
    {
577
      priv = GTK_EVENT_BOX (widget)->priv;
578 579

      if (priv->event_window != NULL)
580 581 582 583 584 585
        gdk_window_move_resize (priv->event_window,
                                child_allocation.x,
                                child_allocation.y,
                                child_allocation.width,
                                child_allocation.height);

586
      if (gtk_widget_get_has_window (widget))
587 588 589 590 591
        gdk_window_move_resize (gtk_widget_get_window (widget),
                                allocation->x,
                                allocation->y,
                                child_allocation.width,
                                child_allocation.height);
Elliot Lee's avatar
Elliot Lee committed
592
    }
Javier Jardón's avatar
Javier Jardón committed
593

594
  baseline = gtk_widget_get_allocated_baseline (widget);
Javier Jardón's avatar
Javier Jardón committed
595 596
  child = gtk_bin_get_child (bin);
  if (child)
597
    gtk_widget_size_allocate_with_baseline (child, &child_allocation, baseline);
Elliot Lee's avatar
Elliot Lee committed
598 599
}

600
static gboolean
601 602
gtk_event_box_draw (GtkWidget *widget,
                    cairo_t   *cr)
603
{
604 605
  if (gtk_widget_get_has_window (widget) &&
      !gtk_widget_get_app_paintable (widget))
606 607 608 609
    {
      GtkStyleContext *context;

      context = gtk_widget_get_style_context (widget);
610

611 612 613
      gtk_render_background (context, cr, 0, 0,
                             gtk_widget_get_allocated_width (widget),
                             gtk_widget_get_allocated_height (widget));
614 615 616
      gtk_render_frame (context, cr, 0, 0,
                        gtk_widget_get_allocated_width (widget),
                        gtk_widget_get_allocated_height (widget));
617
    }
618

619
  GTK_WIDGET_CLASS (gtk_event_box_parent_class)->draw (widget, cr);
Elliot Lee's avatar
Elliot Lee committed
620 621 622

  return FALSE;
}