]> Pileus Git - ~andy/gtk/blob - gtk/gtkoverlay.c
Change FSF Address
[~andy/gtk] / gtk / gtkoverlay.c
1 /*
2  * gtkoverlay.c
3  * This file is part of gtk
4  *
5  * Copyright (C) 2011 - Ignacio Casal Quinteiro, Mike Krüger
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include "config.h"
22
23 #include "gtkoverlay.h"
24 #include "gtkbuildable.h"
25 #include "gtkscrolledwindow.h"
26 #include "gtkmarshalers.h"
27
28 #include "gtkprivate.h"
29 #include "gtkintl.h"
30
31 /**
32  * SECTION:gtkoverlay
33  * @short_description: A container which overlays widgets on top of each other
34  * @title: GtkOverlay
35  *
36  * GtkOverlay is a container which contains a single main child, on top
37  * of which it can place <firstterm>overlay</firstterm> widgets. The
38  * position of each overlay widget is determined by its #GtkWidget:halign
39  * and #GtkWidget:valign properties. E.g. a widget with both alignments
40  * set to %GTK_ALIGN_START will be placed at the top left corner of the
41  * main widget, whereas an overlay with halign set to %GTK_ALIGN_CENTER
42  * and valign set to %GTK_ALIGN_END will be placed a the bottom edge of
43  * the main widget, horizontally centered. The position can be adjusted
44  * by setting the margin properties of the child to non-zero values.
45  *
46  * More complicated placement of overlays is possible by connecting
47  * to the #GtkOverlay::get-child-position signal.
48  *
49  * <refsect2 id="GtkOverlay-BUILDER-UI">
50  * <title>GtkOverlay as GtkBuildable</title>
51  * <para>
52  * The GtkOverlay implementation of the GtkBuildable interface
53  * supports placing a child as an overlay by specifying "overlay" as
54  * the "type" attribute of a <tag class="starttag">child</tag> element.
55  * </para>
56  * </refsect2>
57  */
58
59 struct _GtkOverlayPrivate
60 {
61   GSList *children;
62 };
63
64 typedef struct _GtkOverlayChild GtkOverlayChild;
65
66 struct _GtkOverlayChild
67 {
68   GtkWidget *widget;
69   GdkWindow *window;
70 };
71
72 enum {
73   GET_CHILD_POSITION,
74   LAST_SIGNAL
75 };
76
77 static guint signals[LAST_SIGNAL] = { 0 };
78
79 static void gtk_overlay_buildable_init (GtkBuildableIface *iface);
80
81 G_DEFINE_TYPE_WITH_CODE (GtkOverlay, gtk_overlay, GTK_TYPE_BIN,
82                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
83                                                 gtk_overlay_buildable_init))
84
85 static GdkWindow *
86 gtk_overlay_create_child_window (GtkOverlay *overlay,
87                                  GtkWidget  *child)
88 {
89   GtkWidget *widget = GTK_WIDGET (overlay);
90   GtkAllocation allocation;
91   GdkWindow *window;
92   GdkWindowAttr attributes;
93   gint attributes_mask;
94
95   gtk_widget_get_allocation (child, &allocation);
96
97   attributes.window_type = GDK_WINDOW_CHILD;
98   attributes.wclass = GDK_INPUT_OUTPUT;
99   attributes.width = allocation.width;
100   attributes.height = allocation.height;
101   attributes.x = allocation.x;
102   attributes.y = allocation.y;
103   attributes_mask = GDK_WA_X | GDK_WA_Y;
104   attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
105
106   window = gdk_window_new (gtk_widget_get_window (widget),
107                            &attributes, attributes_mask);
108   gdk_window_set_user_data (window, overlay);
109   gtk_style_context_set_background (gtk_widget_get_style_context (widget), window);
110
111   gtk_widget_set_parent_window (child, window);
112
113   return window;
114 }
115
116 static void
117 gtk_overlay_child_allocate (GtkOverlay      *overlay,
118                             GtkOverlayChild *child)
119 {
120   gint left, right, top, bottom;
121   GtkAllocation allocation, child_allocation, overlay_allocation;
122   gboolean result;
123
124   if (gtk_widget_get_mapped (GTK_WIDGET (overlay)))
125     {
126       if (gtk_widget_get_visible (child->widget))
127         gdk_window_show (child->window);
128       else if (gdk_window_is_visible (child->window))
129         gdk_window_hide (child->window);
130     }
131
132   if (!gtk_widget_get_visible (child->widget))
133     return;
134
135   g_signal_emit (overlay, signals[GET_CHILD_POSITION],
136                  0, child->widget, &allocation, &result);
137
138   gtk_widget_get_allocation (GTK_WIDGET (overlay), &overlay_allocation);
139
140   allocation.x += overlay_allocation.x;
141   allocation.y += overlay_allocation.y;
142
143   /* put the margins outside the window; also arrange things
144    * so that the adjusted child allocation still ends up at 0, 0
145    */
146   left = gtk_widget_get_margin_left (child->widget);
147   right = gtk_widget_get_margin_right (child->widget);
148   top = gtk_widget_get_margin_top (child->widget);
149   bottom = gtk_widget_get_margin_bottom (child->widget);
150
151   child_allocation.x = - left;
152   child_allocation.y = - top;
153   child_allocation.width = allocation.width;
154   child_allocation.height = allocation.height;
155
156   allocation.x += left;
157   allocation.y += top;
158   allocation.width -= left + right;
159   allocation.height -= top + bottom;
160
161   if (child->window)
162     gdk_window_move_resize (child->window,
163                             allocation.x, allocation.y,
164                             allocation.width, allocation.height);
165
166   gtk_widget_size_allocate (child->widget, &child_allocation);
167 }
168
169 static void
170 gtk_overlay_get_preferred_width (GtkWidget *widget,
171                                  gint      *minimum,
172                                  gint      *natural)
173 {
174   GtkBin *bin = GTK_BIN (widget);
175   GtkWidget *child;
176
177   if (minimum)
178     *minimum = 0;
179
180   if (natural)
181     *natural = 0;
182
183   child = gtk_bin_get_child (bin);
184   if (child && gtk_widget_get_visible (child))
185     gtk_widget_get_preferred_width (child, minimum, natural);
186 }
187
188 static void
189 gtk_overlay_get_preferred_height (GtkWidget *widget,
190                                   gint      *minimum,
191                                   gint      *natural)
192 {
193   GtkBin *bin = GTK_BIN (widget);
194   GtkWidget *child;
195
196   if (minimum)
197     *minimum = 0;
198
199   if (natural)
200     *natural = 0;
201
202   child = gtk_bin_get_child (bin);
203   if (child && gtk_widget_get_visible (child))
204     gtk_widget_get_preferred_height (child, minimum, natural);
205 }
206
207 static void
208 gtk_overlay_size_allocate (GtkWidget     *widget,
209                            GtkAllocation *allocation)
210 {
211   GtkOverlay *overlay = GTK_OVERLAY (widget);
212   GtkOverlayPrivate *priv = overlay->priv;
213   GSList *children;
214   GtkWidget *main_widget;
215
216   GTK_WIDGET_CLASS (gtk_overlay_parent_class)->size_allocate (widget, allocation);
217
218   main_widget = gtk_bin_get_child (GTK_BIN (overlay));
219   if (!main_widget || !gtk_widget_get_visible (main_widget))
220     return;
221
222   gtk_widget_size_allocate (main_widget, allocation);
223
224   for (children = priv->children; children; children = children->next)
225     {
226       gtk_overlay_child_allocate (overlay, children->data);
227     }
228 }
229
230 static GtkAlign
231 effective_align (GtkAlign         align,
232                  GtkTextDirection direction)
233 {
234   switch (align)
235     {
236     case GTK_ALIGN_START:
237       return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_END : GTK_ALIGN_START;
238     case GTK_ALIGN_END:
239       return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_START : GTK_ALIGN_END;
240     default:
241       return align;
242     }
243 }
244
245 static gboolean
246 gtk_overlay_get_child_position (GtkOverlay    *overlay,
247                                 GtkWidget     *widget,
248                                 GtkAllocation *alloc)
249 {
250   GtkWidget *main_widget;
251   GtkAllocation main_alloc;
252   GtkRequisition req;
253   GtkAlign halign;
254   GtkTextDirection direction;
255
256   main_widget = gtk_bin_get_child (GTK_BIN (overlay));
257
258   /* special-case scrolled windows */
259   if (GTK_IS_SCROLLED_WINDOW (main_widget))
260     {
261       GtkWidget *grandchild;
262       gint x, y;
263
264       grandchild = gtk_bin_get_child (GTK_BIN (main_widget));
265       gtk_widget_translate_coordinates (grandchild, main_widget, 0, 0, &x, &y);
266
267       main_alloc.x = x;
268       main_alloc.y = y;
269       main_alloc.width = gtk_widget_get_allocated_width (grandchild);
270       main_alloc.height = gtk_widget_get_allocated_height (grandchild);
271     }
272   else
273     {
274       main_alloc.x = 0;
275       main_alloc.y = 0;
276       main_alloc.width = gtk_widget_get_allocated_width (main_widget);
277       main_alloc.height = gtk_widget_get_allocated_height (main_widget);
278     }
279
280   gtk_widget_get_preferred_size (widget, NULL, &req);
281
282   alloc->x = main_alloc.x;
283   alloc->width = MIN (main_alloc.width, req.width);
284
285   direction = gtk_widget_get_direction (widget);
286
287   halign = gtk_widget_get_halign (widget);
288   switch (effective_align (halign, direction))
289     {
290     case GTK_ALIGN_START:
291       /* nothing to do */
292       break;
293     case GTK_ALIGN_FILL:
294       alloc->width = main_alloc.width;
295       break;
296     case GTK_ALIGN_CENTER:
297       alloc->x += main_alloc.width / 2 - req.width / 2;
298       break;
299     case GTK_ALIGN_END:
300       alloc->x += main_alloc.width - req.width;
301       break;
302     }
303
304   alloc->y = main_alloc.y;
305   alloc->height = MIN (main_alloc.height, req.height);
306
307   switch (gtk_widget_get_valign (widget))
308     {
309     case GTK_ALIGN_START:
310       /* nothing to do */
311       break;
312     case GTK_ALIGN_FILL:
313       alloc->height = main_alloc.height;
314       break;
315     case GTK_ALIGN_CENTER:
316       alloc->y += main_alloc.height / 2 - req.height / 2;
317       break;
318     case GTK_ALIGN_END:
319       alloc->y += main_alloc.height - req.height;
320       break;
321     }
322
323   return TRUE;
324 }
325
326 static void
327 gtk_overlay_realize (GtkWidget *widget)
328 {
329   GtkOverlay *overlay = GTK_OVERLAY (widget);
330   GtkOverlayPrivate *priv = overlay->priv;
331   GtkOverlayChild *child;
332   GSList *children;
333
334   GTK_WIDGET_CLASS (gtk_overlay_parent_class)->realize (widget);
335
336   for (children = priv->children; children; children = children->next)
337     {
338       child = children->data;
339
340       if (child->window == NULL)
341         child->window = gtk_overlay_create_child_window (overlay, child->widget);
342     }
343 }
344
345 static void
346 gtk_overlay_unrealize (GtkWidget *widget)
347 {
348   GtkOverlay *overlay = GTK_OVERLAY (widget);
349   GtkOverlayPrivate *priv = overlay->priv;
350   GtkOverlayChild *child;
351   GSList *children;
352
353   for (children = priv->children; children; children = children->next)
354     {
355       child = children->data;
356
357       gtk_widget_set_parent_window (child->widget, NULL);
358       gdk_window_set_user_data (child->window, NULL);
359       gdk_window_destroy (child->window);
360       child->window = NULL;
361     }
362
363   GTK_WIDGET_CLASS (gtk_overlay_parent_class)->unrealize (widget);
364 }
365
366 static void
367 gtk_overlay_map (GtkWidget *widget)
368 {
369   GtkOverlay *overlay = GTK_OVERLAY (widget);
370   GtkOverlayPrivate *priv = overlay->priv;
371   GtkOverlayChild *child;
372   GSList *children;
373
374   GTK_WIDGET_CLASS (gtk_overlay_parent_class)->map (widget);
375
376   for (children = priv->children; children; children = children->next)
377     {
378       child = children->data;
379
380       if (child->window != NULL &&
381           gtk_widget_get_visible (child->widget) &&
382           gtk_widget_get_child_visible (child->widget))
383         gdk_window_show (child->window);
384     }
385 }
386
387 static void
388 gtk_overlay_unmap (GtkWidget *widget)
389 {
390   GtkOverlay *overlay = GTK_OVERLAY (widget);
391   GtkOverlayPrivate *priv = overlay->priv;
392   GtkOverlayChild *child;
393   GSList *children;
394
395   for (children = priv->children; children; children = children->next)
396     {
397       child = children->data;
398
399       if (child->window != NULL &&
400           gdk_window_is_visible (child->window))
401         gdk_window_hide (child->window);
402     }
403
404   GTK_WIDGET_CLASS (gtk_overlay_parent_class)->unmap (widget);
405 }
406
407 static gboolean
408 gtk_overlay_draw (GtkWidget *widget,
409                   cairo_t   *cr)
410 {
411   GtkOverlay *overlay = GTK_OVERLAY (widget);
412   GtkOverlayPrivate *priv = overlay->priv;
413   GtkOverlayChild *child;
414   GSList *children;
415
416   for (children = priv->children; children; children = children->next)
417     {
418       child = children->data;
419
420       if (gtk_cairo_should_draw_window (cr, child->window))
421         {
422           cairo_save (cr);
423           gtk_cairo_transform_to_window (cr, widget, child->window);
424           gtk_render_background (gtk_widget_get_style_context (widget),
425                                  cr,
426                                  0, 0,
427                                  gdk_window_get_width (child->window),
428                                  gdk_window_get_height (child->window));
429           cairo_restore (cr);
430         }
431     }
432
433   GTK_WIDGET_CLASS (gtk_overlay_parent_class)->draw (widget, cr);
434
435   return FALSE;
436 }
437
438 static void
439 gtk_overlay_remove (GtkContainer *container,
440                     GtkWidget    *widget)
441 {
442   GtkOverlayPrivate *priv = GTK_OVERLAY (container)->priv;
443   GtkOverlayChild *child;
444   GSList *children;
445
446   for (children = priv->children; children; children = children->next)
447     {
448       child = children->data;
449
450       if (child->widget == widget)
451         {
452           if (child->window != NULL)
453             {
454               gdk_window_set_user_data (child->window, NULL);
455               gdk_window_destroy (child->window);
456             }
457
458           gtk_widget_unparent (widget);
459
460           priv->children = g_slist_delete_link (priv->children, children);
461           g_slice_free (GtkOverlayChild, child);
462
463           return;
464         }
465     }
466
467   GTK_CONTAINER_CLASS (gtk_overlay_parent_class)->remove (container, widget);
468 }
469
470 static void
471 gtk_overlay_forall (GtkContainer *overlay,
472                     gboolean      include_internals,
473                     GtkCallback   callback,
474                     gpointer      callback_data)
475 {
476   GtkOverlayPrivate *priv = GTK_OVERLAY (overlay)->priv;
477   GtkOverlayChild *child;
478   GSList *children;
479   GtkWidget *main_widget;
480
481   main_widget = gtk_bin_get_child (GTK_BIN (overlay));
482   if (main_widget)
483     (* callback) (main_widget, callback_data);
484
485   children = priv->children;
486   while (children)
487     {
488       child = children->data;
489       children = children->next;
490
491       (* callback) (child->widget, callback_data);
492     }
493 }
494
495 static void
496 gtk_overlay_class_init (GtkOverlayClass *klass)
497 {
498   GObjectClass *object_class = G_OBJECT_CLASS (klass);
499   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
500   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
501
502   widget_class->get_preferred_width = gtk_overlay_get_preferred_width;
503   widget_class->get_preferred_height = gtk_overlay_get_preferred_height;
504   widget_class->size_allocate = gtk_overlay_size_allocate;
505   widget_class->realize = gtk_overlay_realize;
506   widget_class->unrealize = gtk_overlay_unrealize;
507   widget_class->map = gtk_overlay_map;
508   widget_class->unmap = gtk_overlay_unmap;
509   widget_class->draw = gtk_overlay_draw;
510
511   container_class->remove = gtk_overlay_remove;
512   container_class->forall = gtk_overlay_forall;
513
514   klass->get_child_position = gtk_overlay_get_child_position;
515
516   /**
517    * GtkOverlay::get-child-position:
518    * @overlay: the #GtkOverlay
519    * @widget: the child widget to position
520    * @allocation: (out): return location for the allocation
521    *
522    * The ::get-child-position signal is emitted to determine
523    * the position and size of any overlay child widgets. A
524    * handler for this signal should fill @allocation with
525    * the desired position and size for @widget, relative to
526    * the 'main' child of @overlay.
527    *
528    * The default handler for this signal uses the @widget's
529    * halign and valign properties to determine the position
530    * and gives the widget its natural size (except that an
531    * alignment of %GTK_ALIGN_FILL will cause the overlay to
532    * be full-width/height). If the main child is a
533    * #GtkScrolledWindow, the overlays are placed relative
534    * to its contents.
535    *
536    * Return: %TRUE if the @allocation has been filled
537    */
538   signals[GET_CHILD_POSITION] =
539     g_signal_new (I_("get-child-position"),
540                   G_TYPE_FROM_CLASS (object_class),
541                   G_SIGNAL_RUN_LAST,
542                   G_STRUCT_OFFSET (GtkOverlayClass, get_child_position),
543                   _gtk_boolean_handled_accumulator, NULL,
544                   _gtk_marshal_BOOLEAN__OBJECT_BOXED,
545                   G_TYPE_BOOLEAN, 2,
546                   GTK_TYPE_WIDGET,
547                   GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE);
548
549   g_type_class_add_private (object_class, sizeof (GtkOverlayPrivate));
550 }
551
552 static void
553 gtk_overlay_init (GtkOverlay *overlay)
554 {
555   overlay->priv = G_TYPE_INSTANCE_GET_PRIVATE (overlay, GTK_TYPE_OVERLAY, GtkOverlayPrivate);
556
557   gtk_widget_set_has_window (GTK_WIDGET (overlay), FALSE);
558 }
559
560 static void
561 gtk_overlay_buildable_add_child (GtkBuildable *buildable,
562                                  GtkBuilder   *builder,
563                                  GObject      *child,
564                                  const gchar  *type)
565 {
566   if (type && strcmp (type, "overlay") == 0)
567     gtk_overlay_add_overlay (GTK_OVERLAY (buildable), GTK_WIDGET (child));
568   else if (!type)
569     gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
570   else
571     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
572 }
573
574 static void
575 gtk_overlay_buildable_init (GtkBuildableIface *iface)
576 {
577   iface->add_child = gtk_overlay_buildable_add_child;
578 }
579
580 /**
581  * gtk_overlay_new:
582  *
583  * Creates a new #GtkOverlay.
584  *
585  * Returns: a new #GtkOverlay object.
586  *
587  * Since: 3.2
588  */
589 GtkWidget *
590 gtk_overlay_new (void)
591 {
592   return g_object_new (GTK_TYPE_OVERLAY, NULL);
593 }
594
595 /**
596  * gtk_overlay_add_overlay:
597  * @overlay: a #GtkOverlay
598  * @widget: a #GtkWidget to be added to the container
599  *
600  * Adds @widget to @overlay.
601  *
602  * The widget will be stacked on top of the main widget
603  * added with gtk_container_add().
604  *
605  * The position at which @widget is placed is determined
606  * from its #GtkWidget:halign and #GtkWidget:valign properties.
607  *
608  * Since: 3.2
609  */
610 void
611 gtk_overlay_add_overlay (GtkOverlay *overlay,
612                          GtkWidget  *widget)
613 {
614   GtkOverlayPrivate *priv = overlay->priv;
615   GtkOverlayChild *child;
616
617   g_return_if_fail (GTK_IS_OVERLAY (overlay));
618   g_return_if_fail (GTK_IS_WIDGET (widget));
619
620   child = g_slice_new0 (GtkOverlayChild);
621   child->widget = widget;
622
623   priv->children = g_slist_append (priv->children, child);
624
625   if (gtk_widget_get_realized (GTK_WIDGET (overlay)))
626     {
627       child->window = gtk_overlay_create_child_window (overlay, widget);
628       gtk_widget_set_parent (widget, GTK_WIDGET (overlay));
629       gtk_overlay_child_allocate (overlay, child);
630     }
631   else
632     gtk_widget_set_parent (widget, GTK_WIDGET (overlay));
633
634 }