1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20  * file for a list of people on the GTK+ Team.  See the ChangeLog
21  * 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 
27 #include "gtkeventbox.h"
28 
29 #include "gtkintl.h"
30 #include "gtkprivate.h"
31 #include "gtkrender.h"
32 #include "gtksizerequest.h"
33 
34 
35 /**
36  * SECTION:gtkeventbox
37  * @Short_description: A widget used to catch events for widgets which
38  *     do not have their own window
39  * @Title: GtkEventBox
40  *
41  * The #GtkEventBox widget is a subclass of #GtkBin which also has its
42  * own window. It is useful since it allows you to catch events for widgets
43  * which do not have their own window.
44  */
45 
46 struct _GtkEventBoxPrivate
47 {
48   gboolean above_child;
49   GdkWindow *event_window;
50 };
51 
52 enum {
53   PROP_0,
54   PROP_VISIBLE_WINDOW,
55   PROP_ABOVE_CHILD
56 };
57 
58 static void     gtk_event_box_realize       (GtkWidget        *widget);
59 static void     gtk_event_box_unrealize     (GtkWidget        *widget);
60 static void     gtk_event_box_map           (GtkWidget        *widget);
61 static void     gtk_event_box_unmap         (GtkWidget        *widget);
62 static void     gtk_event_box_get_preferred_width  (GtkWidget *widget,
63                                                     gint      *minimum,
64                                                     gint      *natural);
65 static void     gtk_event_box_get_preferred_height (GtkWidget *widget,
66                                                     gint      *minimum,
67                                                     gint      *natural);
68 static void     gtk_event_box_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
69 									   gint       width,
70 									   gint      *minimum,
71 									   gint      *natural,
72 									   gint      *minimum_baseline,
73 									   gint      *natural_baseline);
74 static void     gtk_event_box_size_allocate (GtkWidget        *widget,
75                                              GtkAllocation    *allocation);
76 static gboolean gtk_event_box_draw          (GtkWidget        *widget,
77                                              cairo_t          *cr);
78 static void     gtk_event_box_set_property  (GObject          *object,
79                                              guint             prop_id,
80                                              const GValue     *value,
81                                              GParamSpec       *pspec);
82 static void     gtk_event_box_get_property  (GObject          *object,
83                                              guint             prop_id,
84                                              GValue           *value,
85                                              GParamSpec       *pspec);
86 
G_DEFINE_TYPE_WITH_PRIVATE(GtkEventBox,gtk_event_box,GTK_TYPE_BIN)87 G_DEFINE_TYPE_WITH_PRIVATE (GtkEventBox, gtk_event_box, GTK_TYPE_BIN)
88 
89 static void
90 gtk_event_box_class_init (GtkEventBoxClass *class)
91 {
92   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
93   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
94   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
95 
96   gobject_class->set_property = gtk_event_box_set_property;
97   gobject_class->get_property = gtk_event_box_get_property;
98 
99   widget_class->realize = gtk_event_box_realize;
100   widget_class->unrealize = gtk_event_box_unrealize;
101   widget_class->map = gtk_event_box_map;
102   widget_class->unmap = gtk_event_box_unmap;
103   widget_class->get_preferred_width = gtk_event_box_get_preferred_width;
104   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;
106   widget_class->size_allocate = gtk_event_box_size_allocate;
107   widget_class->draw = gtk_event_box_draw;
108 
109   gtk_container_class_handle_border_width (container_class);
110 
111   g_object_class_install_property (gobject_class,
112                                    PROP_VISIBLE_WINDOW,
113                                    g_param_spec_boolean ("visible-window",
114                                                         P_("Visible Window"),
115                                                         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   g_object_class_install_property (gobject_class,
119                                    PROP_ABOVE_CHILD,
120                                    g_param_spec_boolean ("above-child",
121                                                         P_("Above child"),
122                                                         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));
125 }
126 
127 static void
gtk_event_box_init(GtkEventBox * event_box)128 gtk_event_box_init (GtkEventBox *event_box)
129 {
130   gtk_widget_set_has_window (GTK_WIDGET (event_box), TRUE);
131 
132   event_box->priv = gtk_event_box_get_instance_private (event_box);
133   event_box->priv->above_child = FALSE;
134 }
135 
136 /**
137  * gtk_event_box_new:
138  *
139  * Creates a new #GtkEventBox.
140  *
141  * Returns: a new #GtkEventBox
142  */
143 GtkWidget*
gtk_event_box_new(void)144 gtk_event_box_new (void)
145 {
146   return g_object_new (GTK_TYPE_EVENT_BOX, NULL);
147 }
148 
149 static void
gtk_event_box_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)150 gtk_event_box_set_property (GObject      *object,
151                             guint         prop_id,
152                             const GValue *value,
153                             GParamSpec   *pspec)
154 {
155   GtkEventBox *event_box;
156 
157   event_box = GTK_EVENT_BOX (object);
158 
159   switch (prop_id)
160     {
161     case PROP_VISIBLE_WINDOW:
162       gtk_event_box_set_visible_window (event_box, g_value_get_boolean (value));
163       break;
164 
165     case PROP_ABOVE_CHILD:
166       gtk_event_box_set_above_child (event_box, g_value_get_boolean (value));
167       break;
168 
169     default:
170       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
171       break;
172     }
173 }
174 
175 static void
gtk_event_box_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)176 gtk_event_box_get_property (GObject     *object,
177                             guint        prop_id,
178                             GValue      *value,
179                             GParamSpec  *pspec)
180 {
181   GtkEventBox *event_box;
182 
183   event_box = GTK_EVENT_BOX (object);
184 
185   switch (prop_id)
186     {
187     case PROP_VISIBLE_WINDOW:
188       g_value_set_boolean (value, gtk_event_box_get_visible_window (event_box));
189       break;
190 
191     case PROP_ABOVE_CHILD:
192       g_value_set_boolean (value, gtk_event_box_get_above_child (event_box));
193       break;
194 
195     default:
196       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
197       break;
198     }
199 }
200 
201 /**
202  * gtk_event_box_get_visible_window:
203  * @event_box: a #GtkEventBox
204  *
205  * Returns whether the event box has a visible window.
206  * See gtk_event_box_set_visible_window() for details.
207  *
208  * Returns: %TRUE if the event box window is visible
209  *
210  * Since: 2.4
211  */
212 gboolean
gtk_event_box_get_visible_window(GtkEventBox * event_box)213 gtk_event_box_get_visible_window (GtkEventBox *event_box)
214 {
215   g_return_val_if_fail (GTK_IS_EVENT_BOX (event_box), FALSE);
216 
217   return gtk_widget_get_has_window (GTK_WIDGET (event_box));
218 }
219 
220 /**
221  * gtk_event_box_set_visible_window:
222  * @event_box: a #GtkEventBox
223  * @visible_window: %TRUE to make the event box have a visible window
224  *
225  * Set whether the event box uses a visible or invisible child
226  * window. The default is to use visible windows.
227  *
228  * In an invisible window event box, the window that the
229  * event box creates is a %GDK_INPUT_ONLY window, which
230  * means that it is invisible and only serves to receive
231  * 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  * You should generally make your event box invisible if
238  * you just want to trap events. Creating a visible window
239  * may cause artifacts that are visible to the user, especially
240  * if the user is using a theme with gradients or pixmaps.
241  *
242  * The main reason to create a non input-only event box is if
243  * you want to set the background to a different color or
244  * draw on it.
245  *
246  * There is one unexpected issue for an invisible event box that has its
247  * window below the child. (See gtk_event_box_set_above_child().)
248  * 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  * windows aren’t propagated up by the windowing system, but only by GTK+.
251  * 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  * that case, the event box window is actually the ancestor of the
257  * descendant windows, not just at the same place on the screen.
258  *
259  * Since: 2.4
260  */
261 void
gtk_event_box_set_visible_window(GtkEventBox * event_box,gboolean visible_window)262 gtk_event_box_set_visible_window (GtkEventBox *event_box,
263                                   gboolean     visible_window)
264 {
265   GtkWidget *widget;
266 
267   g_return_if_fail (GTK_IS_EVENT_BOX (event_box));
268 
269   widget = GTK_WIDGET (event_box);
270 
271   visible_window = visible_window != FALSE;
272 
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           if (visible)
280             gtk_widget_hide (widget);
281 
282           gtk_widget_unrealize (widget);
283 
284           gtk_widget_set_has_window (widget, visible_window);
285 
286           gtk_widget_realize (widget);
287 
288           if (visible)
289             gtk_widget_show (widget);
290         }
291       else
292         {
293           gtk_widget_set_has_window (widget, visible_window);
294         }
295 
296       if (gtk_widget_get_visible (widget))
297         gtk_widget_queue_resize (widget);
298 
299       g_object_notify (G_OBJECT (event_box), "visible-window");
300     }
301 }
302 
303 /**
304  * gtk_event_box_get_above_child:
305  * @event_box: a #GtkEventBox
306  *
307  * Returns whether the event box window is above or below the
308  * windows of its child. See gtk_event_box_set_above_child()
309  * 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 gboolean
gtk_event_box_get_above_child(GtkEventBox * event_box)317 gtk_event_box_get_above_child (GtkEventBox *event_box)
318 {
319   GtkEventBoxPrivate *priv = event_box->priv;
320 
321   g_return_val_if_fail (GTK_IS_EVENT_BOX (event_box), FALSE);
322 
323   return priv->above_child;
324 }
325 
326 /**
327  * gtk_event_box_set_above_child:
328  * @event_box: a #GtkEventBox
329  * @above_child: %TRUE if the event box window is above its child
330  *
331  * Set whether the event box window is positioned above the windows
332  * of its child, as opposed to below it. If the window is above, all
333  * events inside the event box will go to the event box. If the window
334  * is below, events in windows of child widgets will first got to that
335  * 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 void
gtk_event_box_set_above_child(GtkEventBox * event_box,gboolean above_child)342 gtk_event_box_set_above_child (GtkEventBox *event_box,
343                                gboolean     above_child)
344 {
345   GtkEventBoxPrivate *priv = event_box->priv;
346   GtkWidget *widget;
347 
348   g_return_if_fail (GTK_IS_EVENT_BOX (event_box));
349 
350   widget = GTK_WIDGET (event_box);
351 
352   above_child = above_child != FALSE;
353 
354   if (priv->above_child != above_child)
355     {
356       priv->above_child = above_child;
357 
358       if (gtk_widget_get_realized (widget))
359         {
360           if (!gtk_widget_get_has_window (widget))
361             {
362               if (above_child)
363                 gdk_window_raise (priv->event_window);
364               else
365                 gdk_window_lower (priv->event_window);
366             }
367           else
368             {
369               gboolean visible = gtk_widget_get_visible (widget);
370 
371               if (visible)
372                 gtk_widget_hide (widget);
373 
374               gtk_widget_unrealize (widget);
375               gtk_widget_realize (widget);
376 
377               if (visible)
378                 gtk_widget_show (widget);
379             }
380         }
381 
382       if (gtk_widget_get_visible (widget))
383         gtk_widget_queue_resize (widget);
384 
385       g_object_notify (G_OBJECT (event_box), "above-child");
386     }
387 }
388 
389 static void
gtk_event_box_realize(GtkWidget * widget)390 gtk_event_box_realize (GtkWidget *widget)
391 {
392   GtkEventBoxPrivate *priv;
393   GtkAllocation allocation;
394   GdkWindow *window;
395   GdkWindowAttr attributes;
396   gint attributes_mask;
397   gboolean visible_window;
398 
399   gtk_widget_get_allocation (widget, &allocation);
400 
401   gtk_widget_set_realized (widget, TRUE);
402 
403   attributes.x = allocation.x;
404   attributes.y = allocation.y;
405   attributes.width = allocation.width;
406   attributes.height = allocation.height;
407   attributes.window_type = GDK_WINDOW_CHILD;
408   attributes.event_mask = gtk_widget_get_events (widget)
409                         | GDK_BUTTON_MOTION_MASK
410                         | GDK_BUTTON_PRESS_MASK
411                         | GDK_BUTTON_RELEASE_MASK
412                         | GDK_EXPOSURE_MASK
413                         | GDK_ENTER_NOTIFY_MASK
414                         | GDK_LEAVE_NOTIFY_MASK;
415 
416   priv = GTK_EVENT_BOX (widget)->priv;
417 
418   visible_window = gtk_widget_get_has_window (widget);
419   if (visible_window)
420     {
421       attributes.visual = gtk_widget_get_visual (widget);
422       attributes.wclass = GDK_INPUT_OUTPUT;
423 
424       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
425 
426       window = gdk_window_new (gtk_widget_get_parent_window (widget),
427                                &attributes, attributes_mask);
428       gtk_widget_set_window (widget, window);
429       gtk_widget_register_window (widget, window);
430     }
431   else
432     {
433       window = gtk_widget_get_parent_window (widget);
434       gtk_widget_set_window (widget, window);
435       g_object_ref (window);
436     }
437 
438   if (!visible_window || priv->above_child)
439     {
440       attributes.wclass = GDK_INPUT_ONLY;
441       if (!visible_window)
442         attributes_mask = GDK_WA_X | GDK_WA_Y;
443       else
444         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 static void
gtk_event_box_unrealize(GtkWidget * widget)453 gtk_event_box_unrealize (GtkWidget *widget)
454 {
455   GtkEventBoxPrivate *priv = GTK_EVENT_BOX (widget)->priv;
456 
457   if (priv->event_window != NULL)
458     {
459       gtk_widget_unregister_window (widget, priv->event_window);
460       gdk_window_destroy (priv->event_window);
461       priv->event_window = NULL;
462     }
463 
464   GTK_WIDGET_CLASS (gtk_event_box_parent_class)->unrealize (widget);
465 }
466 
467 static void
gtk_event_box_map(GtkWidget * widget)468 gtk_event_box_map (GtkWidget *widget)
469 {
470   GtkEventBoxPrivate *priv = GTK_EVENT_BOX (widget)->priv;
471 
472   if (priv->event_window != NULL && !priv->above_child)
473     gdk_window_show (priv->event_window);
474 
475   GTK_WIDGET_CLASS (gtk_event_box_parent_class)->map (widget);
476 
477   if (priv->event_window != NULL && priv->above_child)
478     gdk_window_show (priv->event_window);
479 }
480 
481 static void
gtk_event_box_unmap(GtkWidget * widget)482 gtk_event_box_unmap (GtkWidget *widget)
483 {
484   GtkEventBoxPrivate *priv = GTK_EVENT_BOX (widget)->priv;
485 
486   if (priv->event_window != NULL)
487     gdk_window_hide (priv->event_window);
488 
489   GTK_WIDGET_CLASS (gtk_event_box_parent_class)->unmap (widget);
490 }
491 
492 static void
gtk_event_box_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)493 gtk_event_box_get_preferred_width (GtkWidget *widget,
494                                    gint      *minimum,
495                                    gint      *natural)
496 {
497   GtkBin *bin = GTK_BIN (widget);
498   GtkWidget *child;
499 
500   *minimum = 0;
501   *natural = 0;
502 
503   child = gtk_bin_get_child (bin);
504   if (child && gtk_widget_get_visible (child))
505     gtk_widget_get_preferred_width (child, minimum, natural);
506 }
507 
508 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)509 gtk_event_box_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
510 							   gint       width,
511 							   gint      *minimum,
512 							   gint      *natural,
513 							   gint      *minimum_baseline,
514 							   gint      *natural_baseline)
515 {
516   GtkBin *bin = GTK_BIN (widget);
517   GtkWidget *child;
518 
519   *minimum = 0;
520   *natural = 0;
521 
522   if (minimum_baseline)
523     *minimum_baseline = -1;
524 
525   if (natural_baseline)
526     *natural_baseline = -1;
527 
528   child = gtk_bin_get_child (bin);
529   if (child && gtk_widget_get_visible (child))
530     gtk_widget_get_preferred_height_and_baseline_for_width (child,
531 							    width,
532 							    minimum,
533 							    natural,
534 							    minimum_baseline,
535 							    natural_baseline);
536 }
537 
538 static void
gtk_event_box_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)539 gtk_event_box_get_preferred_height (GtkWidget *widget,
540                                     gint      *minimum,
541                                     gint      *natural)
542 {
543   gtk_event_box_get_preferred_height_and_baseline_for_width (widget, -1,
544 							     minimum, natural,
545 							     NULL, NULL);
546 }
547 
548 static void
gtk_event_box_size_allocate(GtkWidget * widget,GtkAllocation * allocation)549 gtk_event_box_size_allocate (GtkWidget     *widget,
550                              GtkAllocation *allocation)
551 {
552   GtkBin *bin;
553   GtkAllocation child_allocation;
554   gint baseline;
555   GtkEventBoxPrivate *priv;
556   GtkWidget *child;
557 
558   bin = GTK_BIN (widget);
559 
560   gtk_widget_set_allocation (widget, allocation);
561 
562   if (!gtk_widget_get_has_window (widget))
563     {
564       child_allocation.x = allocation->x;
565       child_allocation.y = allocation->y;
566     }
567   else
568     {
569       child_allocation.x = 0;
570       child_allocation.y = 0;
571     }
572   child_allocation.width = allocation->width;
573   child_allocation.height = allocation->height;
574 
575   if (gtk_widget_get_realized (widget))
576     {
577       priv = GTK_EVENT_BOX (widget)->priv;
578 
579       if (priv->event_window != NULL)
580         gdk_window_move_resize (priv->event_window,
581                                 child_allocation.x,
582                                 child_allocation.y,
583                                 child_allocation.width,
584                                 child_allocation.height);
585 
586       if (gtk_widget_get_has_window (widget))
587         gdk_window_move_resize (gtk_widget_get_window (widget),
588                                 allocation->x,
589                                 allocation->y,
590                                 child_allocation.width,
591                                 child_allocation.height);
592     }
593 
594   baseline = gtk_widget_get_allocated_baseline (widget);
595   child = gtk_bin_get_child (bin);
596   if (child)
597     gtk_widget_size_allocate_with_baseline (child, &child_allocation, baseline);
598 }
599 
600 static gboolean
gtk_event_box_draw(GtkWidget * widget,cairo_t * cr)601 gtk_event_box_draw (GtkWidget *widget,
602                     cairo_t   *cr)
603 {
604   if (gtk_widget_get_has_window (widget) &&
605       !gtk_widget_get_app_paintable (widget))
606     {
607       GtkStyleContext *context;
608 
609       context = gtk_widget_get_style_context (widget);
610 
611       gtk_render_background (context, cr, 0, 0,
612                              gtk_widget_get_allocated_width (widget),
613                              gtk_widget_get_allocated_height (widget));
614       gtk_render_frame (context, cr, 0, 0,
615                         gtk_widget_get_allocated_width (widget),
616                         gtk_widget_get_allocated_height (widget));
617     }
618 
619   GTK_WIDGET_CLASS (gtk_event_box_parent_class)->draw (widget, cr);
620 
621   return FALSE;
622 }
623