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