]> Pileus Git - ~andy/gtk/blob - gtk/gtkbubblewindow.c
454c63020e40f8cd1320708547e964f69a47c2df
[~andy/gtk] / gtk / gtkbubblewindow.c
1 /* GTK - The GIMP Toolkit
2  * Copyright © 2013 Carlos Garnacho <carlosg@gnome.org>
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  * GtkBubbleWindow is a bubble-like context window, primarily mean for
20  * context-dependent helpers on touch interfaces.
21  *
22  * In order to place a GtkBubbleWindow to point to some other area,
23  * use gtk_bubble_window_set_relative_to(), gtk_bubble_window_set_pointing_to()
24  * and gtk_bubble_window_set_position(). Although it is usually  more
25  * convenient to use gtk_bubble_window_popup() which handles all of those
26  * at once.
27  *
28  * By default, no grabs are performed on the GtkBubbleWindow, leaving
29  * the popup/popdown semantics up to the caller, gtk_bubble_window_grab()
30  * can be used to grab the window for a device pair, bringing #GtkMenu alike
31  * popdown behavior by default on keyboard/pointer interaction. Grabs need
32  * to be undone through gtk_bubble_window_ungrab().
33  */
34
35 #include "config.h"
36 #include <gdk/gdk.h>
37 #include <cairo-gobject.h>
38 #include "gtkbubblewindowprivate.h"
39 #include "gtktypebuiltins.h"
40 #include "gtkmain.h"
41 #include "gtkprivate.h"
42 #include "gtkintl.h"
43
44 #define TAIL_GAP_WIDTH 24
45 #define TAIL_HEIGHT    12
46
47 #define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
48
49 #define GRAB_EVENT_MASK                             \
50   GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
51   GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |       \
52   GDK_POINTER_MOTION_MASK
53
54 typedef struct _GtkBubbleWindowPrivate GtkBubbleWindowPrivate;
55
56 enum {
57   PROP_RELATIVE_TO = 1,
58   PROP_POINTING_TO,
59   PROP_POSITION
60 };
61
62 struct _GtkBubbleWindowPrivate
63 {
64   GdkDevice *device;
65   GdkWindow *relative_to;
66   cairo_rectangle_int_t pointing_to;
67   gint win_x;
68   gint win_y;
69   guint has_pointing_to    : 1;
70   guint grabbed            : 1;
71   guint preferred_position : 2;
72   guint final_position     : 2;
73 };
74
75 G_DEFINE_TYPE (GtkBubbleWindow, _gtk_bubble_window, GTK_TYPE_WINDOW)
76
77 static void
78 _gtk_bubble_window_init (GtkBubbleWindow *window)
79 {
80   GtkWidget *widget;
81   GdkScreen *screen;
82   GdkVisual *visual;
83
84   widget = GTK_WIDGET (window);
85   window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
86                                                GTK_TYPE_BUBBLE_WINDOW,
87                                                GtkBubbleWindowPrivate);
88   gtk_window_set_default_size (GTK_WINDOW (window),
89                                TAIL_GAP_WIDTH, TAIL_GAP_WIDTH);
90   gtk_widget_set_app_paintable (widget, TRUE);
91
92   screen = gtk_widget_get_screen (widget);
93   visual = gdk_screen_get_rgba_visual (screen);
94
95   if (visual)
96     gtk_widget_set_visual (widget, visual);
97 }
98
99 static GObject *
100 gtk_bubble_window_constructor (GType                  type,
101                                guint                  n_construct_properties,
102                                GObjectConstructParam *construct_properties)
103 {
104   GObject *object;
105
106   object =
107     G_OBJECT_CLASS (_gtk_bubble_window_parent_class)->constructor (type,
108                                                                   n_construct_properties,
109                                                                   construct_properties);
110   g_object_set (object, "type", GTK_WINDOW_POPUP, NULL);
111
112   return object;
113 }
114
115 static void
116 gtk_bubble_window_set_property (GObject      *object,
117                                 guint         prop_id,
118                                 const GValue *value,
119                                 GParamSpec   *pspec)
120 {
121   switch (prop_id)
122     {
123     case PROP_RELATIVE_TO:
124       _gtk_bubble_window_set_relative_to (GTK_BUBBLE_WINDOW (object),
125                                           g_value_get_object (value));
126       break;
127     case PROP_POINTING_TO:
128       _gtk_bubble_window_set_pointing_to (GTK_BUBBLE_WINDOW (object),
129                                           g_value_get_boxed (value));
130       break;
131     case PROP_POSITION:
132       _gtk_bubble_window_set_position (GTK_BUBBLE_WINDOW (object),
133                                        g_value_get_enum (value));
134       break;
135     default:
136       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137     }
138 }
139
140 static void
141 gtk_bubble_window_get_property (GObject    *object,
142                                 guint       prop_id,
143                                 GValue     *value,
144                                 GParamSpec *pspec)
145 {
146   GtkBubbleWindowPrivate *priv = GTK_BUBBLE_WINDOW (object)->priv;
147
148   switch (prop_id)
149     {
150     case PROP_RELATIVE_TO:
151       g_value_set_object (value, priv->relative_to);
152       break;
153     case PROP_POINTING_TO:
154       g_value_set_boxed (value, &priv->pointing_to);
155       break;
156     case PROP_POSITION:
157       g_value_set_enum (value, priv->preferred_position);
158       break;
159     default:
160       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
161     }
162 }
163
164 static void
165 gtk_bubble_window_finalize (GObject *object)
166 {
167   GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (object);
168   GtkBubbleWindowPrivate *priv = window->priv;
169
170   _gtk_bubble_window_popdown (window);
171
172   if (priv->relative_to)
173     g_object_unref (priv->relative_to);
174
175   G_OBJECT_CLASS (_gtk_bubble_window_parent_class)->finalize (object);
176 }
177
178 static void
179 gtk_bubble_window_get_pointed_to_coords (GtkBubbleWindow       *window,
180                                          gint                  *x,
181                                          gint                  *y,
182                                          cairo_rectangle_int_t *root_rect)
183 {
184   GtkBubbleWindowPrivate *priv = window->priv;
185   cairo_rectangle_int_t rect;
186   GdkScreen *screen;
187
188   rect = priv->pointing_to;
189   screen = gtk_widget_get_screen (GTK_WIDGET (window));
190
191   if (priv->relative_to)
192     gdk_window_get_root_coords (priv->relative_to,
193                                 rect.x, rect.y, &rect.x, &rect.y);
194
195   if (POS_IS_VERTICAL (priv->final_position))
196     {
197       *x = CLAMP (rect.x + (rect.width / 2),
198                   0, gdk_screen_get_width (screen));
199       *y = rect.y;
200
201       if (priv->final_position == GTK_POS_BOTTOM)
202         (*y) += rect.height;
203     }
204   else
205     {
206       *y = CLAMP (rect.y + (rect.height / 2),
207                   0, gdk_screen_get_height (screen));
208       *x = rect.x;
209
210       if (priv->final_position == GTK_POS_RIGHT)
211         (*x) += rect.width;
212     }
213
214   if (root_rect)
215     *root_rect = rect;
216 }
217
218 static void
219 gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window,
220                                    cairo_t         *cr,
221                                    GtkAllocation   *allocation)
222 {
223   GtkBubbleWindowPrivate *priv = window->priv;
224   gint base, tip, x, y;
225
226   gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL);
227
228   if (priv->final_position == GTK_POS_BOTTOM ||
229       priv->final_position == GTK_POS_RIGHT)
230     {
231       base = TAIL_HEIGHT;
232       tip = 0;
233     }
234   else if (priv->final_position == GTK_POS_TOP)
235     {
236       base = allocation->height - TAIL_HEIGHT;
237       tip = allocation->height;
238     }
239   else if (priv->final_position == GTK_POS_LEFT)
240     {
241       base = allocation->width - TAIL_HEIGHT;
242       tip = allocation->width;
243     }
244
245   if (POS_IS_VERTICAL (priv->final_position))
246     {
247       cairo_move_to (cr, CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2,
248                                 0, allocation->width - TAIL_GAP_WIDTH), base);
249       cairo_line_to (cr, CLAMP (x - priv->win_x, 0, allocation->width), tip);
250       cairo_line_to (cr, CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2,
251                                 TAIL_GAP_WIDTH, allocation->width), base);
252     }
253   else
254     {
255       cairo_move_to (cr, base,
256                      CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2,
257                             0, allocation->height - TAIL_GAP_WIDTH));
258       cairo_line_to (cr, tip, CLAMP (y - priv->win_y, 0, allocation->height));
259       cairo_line_to (cr, base,
260                      CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2,
261                             TAIL_GAP_WIDTH, allocation->height));
262     }
263 }
264
265 static void
266 gtk_bubble_window_apply_border_path (GtkBubbleWindow *window,
267                                      cairo_t         *cr)
268 {
269   GtkBubbleWindowPrivate *priv;
270   GtkAllocation allocation;
271
272   priv = window->priv;
273   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
274
275   gtk_bubble_window_apply_tail_path (window, cr, &allocation);
276
277   if (priv->final_position == GTK_POS_TOP)
278     {
279       cairo_line_to (cr, allocation.width, allocation.height - TAIL_HEIGHT);
280       cairo_line_to (cr, allocation.width, 0);
281       cairo_line_to (cr, 0, 0);
282       cairo_line_to (cr, 0, allocation.height - TAIL_HEIGHT);
283     }
284   else if (priv->final_position == GTK_POS_BOTTOM)
285     {
286       cairo_line_to (cr, allocation.width, TAIL_HEIGHT);
287       cairo_line_to (cr, allocation.width, allocation.height);
288       cairo_line_to (cr, 0, allocation.height);
289       cairo_line_to (cr, 0, TAIL_HEIGHT);
290     }
291   else if (priv->final_position == GTK_POS_LEFT)
292     {
293       cairo_line_to (cr, allocation.width - TAIL_HEIGHT, allocation.height);
294       cairo_line_to (cr, 0, allocation.height);
295       cairo_line_to (cr, 0, 0);
296       cairo_line_to (cr, allocation.width - TAIL_HEIGHT, 0);
297     }
298   else if (priv->final_position == GTK_POS_RIGHT)
299     {
300       cairo_line_to (cr, TAIL_HEIGHT, 0);
301       cairo_line_to (cr, allocation.width, 0);
302       cairo_line_to (cr, allocation.width, allocation.height);
303       cairo_line_to (cr, TAIL_HEIGHT, allocation.height);
304     }
305
306   cairo_close_path (cr);
307 }
308
309 static void
310 gtk_bubble_window_update_shape (GtkBubbleWindow *window)
311 {
312   cairo_surface_t *surface;
313   cairo_region_t *region;
314   GdkWindow *win;
315   cairo_t *cr;
316
317   win = gtk_widget_get_window (GTK_WIDGET (window));
318   surface =
319     gdk_window_create_similar_surface (win,
320                                        CAIRO_CONTENT_COLOR_ALPHA,
321                                        gdk_window_get_width (win),
322                                        gdk_window_get_height (win));
323
324   cr = cairo_create (surface);
325   gtk_bubble_window_apply_border_path (window, cr);
326   cairo_fill (cr);
327   cairo_destroy (cr);
328
329   region = gdk_cairo_region_create_from_surface (surface);
330   cairo_surface_destroy (surface);
331
332   if (!gtk_widget_is_composited (GTK_WIDGET (window)))
333     gtk_widget_shape_combine_region (GTK_WIDGET (window), region);
334
335   gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region);
336   cairo_region_destroy (region);
337 }
338
339 static void
340 gtk_bubble_window_update_position (GtkBubbleWindow *window)
341 {
342   GtkBubbleWindowPrivate *priv;
343   cairo_rectangle_int_t rect;
344   GtkAllocation allocation;
345   gint win_x, win_y, x, y;
346   GdkScreen *screen;
347
348   priv = window->priv;
349   screen = gtk_widget_get_screen (GTK_WIDGET (window));
350   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
351   priv->final_position = priv->preferred_position;
352   rect = priv->pointing_to;
353
354   gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect);
355
356   /* Check whether there's enough room on the
357    * preferred side, move to the opposite one if not.
358    */
359   if (priv->preferred_position == GTK_POS_TOP && rect.y < allocation.height)
360     priv->final_position = GTK_POS_BOTTOM;
361   else if (priv->preferred_position == GTK_POS_BOTTOM &&
362            rect.y > gdk_screen_get_height (screen) - allocation.height)
363     priv->final_position = GTK_POS_TOP;
364   else if (priv->preferred_position == GTK_POS_LEFT && rect.x < allocation.width)
365     priv->final_position = GTK_POS_RIGHT;
366   else if (priv->preferred_position == GTK_POS_RIGHT &&
367            rect.x > gdk_screen_get_width (screen) - allocation.width)
368     priv->final_position = GTK_POS_LEFT;
369
370   if (POS_IS_VERTICAL (priv->final_position))
371     {
372       win_x = CLAMP (x - allocation.width / 2,
373                      0, gdk_screen_get_width (screen) - allocation.width);
374       win_y = y;
375
376       if (priv->final_position == GTK_POS_TOP)
377         win_y -= allocation.height;
378     }
379   else
380     {
381       win_y = CLAMP (y - allocation.height / 2,
382                      0, gdk_screen_get_height (screen) - allocation.height);
383       win_x = x;
384
385       if (priv->final_position == GTK_POS_LEFT)
386         win_x -= allocation.width;
387
388     }
389
390   priv->win_x = win_x;
391   priv->win_y = win_y;
392   gtk_window_move (GTK_WINDOW (window), win_x, win_y);
393
394   gtk_widget_queue_resize (GTK_WIDGET (window));
395 }
396
397 static gboolean
398 gtk_bubble_window_draw (GtkWidget *widget,
399                         cairo_t   *cr)
400 {
401   GtkStyleContext *context;
402   GtkAllocation allocation;
403   GtkWidget *child;
404   GdkRGBA *border;
405
406   cairo_save (cr);
407   context = gtk_widget_get_style_context (widget);
408   gtk_widget_get_allocation (widget, &allocation);
409
410   gtk_render_background (context, cr, 0, 0,
411                          allocation.width, allocation.height);
412
413   if (gtk_widget_is_composited (widget))
414     {
415       cairo_save (cr);
416       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
417       cairo_set_source_rgba (cr, 0, 0, 0, 0);
418       cairo_paint (cr);
419       cairo_restore (cr);
420
421       gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
422       cairo_clip (cr);
423     }
424
425   gtk_render_background (context, cr, 0, 0,
426                          allocation.width, allocation.height);
427
428   gtk_style_context_get (context, gtk_widget_get_state_flags (widget),
429                          GTK_STYLE_PROPERTY_BORDER_COLOR, &border,
430                          NULL);
431
432   gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
433   gdk_cairo_set_source_rgba (cr, border);
434   cairo_stroke (cr);
435
436   child = gtk_bin_get_child (GTK_BIN (widget));
437
438   if (child)
439     gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
440
441   cairo_restore (cr);
442
443   gdk_rgba_free (border);
444
445   return TRUE;
446 }
447
448 static void
449 get_padding_and_border (GtkWidget *widget,
450                         GtkBorder *border)
451 {
452   GtkStyleContext *context;
453   GtkStateFlags state;
454   GtkBorder tmp;
455
456   context = gtk_widget_get_style_context (widget);
457   state = gtk_widget_get_state_flags (widget);
458
459   gtk_style_context_get_padding (context, state, border);
460   gtk_style_context_get_border (context, state, &tmp);
461   border->top += tmp.top;
462   border->right += tmp.right;
463   border->bottom += tmp.bottom;
464   border->left += tmp.left;
465 }
466
467 static void
468 gtk_bubble_window_get_preferred_width (GtkWidget *widget,
469                                        gint      *minimum_width,
470                                        gint      *natural_width)
471 {
472   GtkBubbleWindowPrivate *priv;
473   GtkWidget *child;
474   gint min, nat;
475   GtkBorder border;
476
477   priv = GTK_BUBBLE_WINDOW (widget)->priv;
478   child = gtk_bin_get_child (GTK_BIN (widget));
479   min = nat = 0;
480
481   if (child)
482     gtk_widget_get_preferred_width (child, &min, &nat);
483
484   get_padding_and_border (widget, &border);
485   min += border.left + border.right;
486   nat += border.left + border.right;
487
488   if (!POS_IS_VERTICAL (priv->final_position))
489     {
490       min += TAIL_HEIGHT;
491       nat += TAIL_HEIGHT;
492     }
493
494   if (minimum_width)
495     *minimum_width = MAX (min, TAIL_GAP_WIDTH);
496
497   if (natural_width)
498     *natural_width = MAX (nat, TAIL_GAP_WIDTH);
499 }
500
501 static void
502 gtk_bubble_window_get_preferred_height (GtkWidget *widget,
503                                         gint      *minimum_height,
504                                         gint      *natural_height)
505 {
506   GtkBubbleWindowPrivate *priv;
507   GtkWidget *child;
508   gint min, nat;
509   GtkBorder border;
510
511   priv = GTK_BUBBLE_WINDOW (widget)->priv;
512   child = gtk_bin_get_child (GTK_BIN (widget));
513   min = nat = 0;
514
515   if (child)
516     gtk_widget_get_preferred_height (child, &min, &nat);
517
518   get_padding_and_border (widget, &border);
519   min += border.top + border.bottom;
520   nat += border.top + border.bottom;
521
522   if (POS_IS_VERTICAL (priv->final_position))
523     {
524       min += TAIL_HEIGHT;
525       nat += TAIL_HEIGHT;
526     }
527
528   if (minimum_height)
529     *minimum_height = MAX (min, TAIL_GAP_WIDTH);
530
531   if (natural_height)
532     *natural_height = MAX (nat, TAIL_GAP_WIDTH);
533 }
534
535 static void
536 gtk_bubble_window_size_allocate (GtkWidget     *widget,
537                                  GtkAllocation *allocation)
538 {
539   GtkBubbleWindowPrivate *priv;
540   GtkWidget *child;
541
542   priv = GTK_BUBBLE_WINDOW (widget)->priv;
543   gtk_widget_set_allocation (widget, allocation);
544   child = gtk_bin_get_child (GTK_BIN (widget));
545
546   if (child)
547     {
548       GtkAllocation child_alloc;
549       GtkBorder border;
550
551       get_padding_and_border (widget, &border);
552
553       child_alloc.x = border.left;
554       child_alloc.y = border.top;
555       child_alloc.width = allocation->width - border.left - border.right;
556       child_alloc.height = allocation->height - border.top - border.bottom;
557
558       if (POS_IS_VERTICAL (priv->final_position))
559         child_alloc.height -= TAIL_HEIGHT;
560       else
561         child_alloc.width -= TAIL_HEIGHT;
562
563       if (priv->final_position == GTK_POS_BOTTOM)
564         child_alloc.y += TAIL_HEIGHT;
565       else if (priv->final_position == GTK_POS_RIGHT)
566         child_alloc.x += TAIL_HEIGHT;
567
568       gtk_widget_size_allocate (child, &child_alloc);
569     }
570
571   if (gtk_widget_get_realized (widget))
572     gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget));
573
574   if (gtk_widget_get_visible (widget))
575     gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget));
576 }
577
578 static gboolean
579 gtk_bubble_window_button_press (GtkWidget      *widget,
580                                 GdkEventButton *event)
581 {
582   GtkWidget *child;
583
584   child = gtk_bin_get_child (GTK_BIN (widget));
585
586   if (child && event->window == gtk_widget_get_window (widget))
587     {
588       GtkAllocation child_alloc;
589
590       gtk_widget_get_allocation (child, &child_alloc);
591
592       if (event->x < child_alloc.x ||
593           event->x > child_alloc.x + child_alloc.width ||
594           event->y < child_alloc.y ||
595           event->y > child_alloc.y + child_alloc.height)
596         _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
597     }
598   else
599     _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
600
601   return GDK_EVENT_PROPAGATE;
602 }
603
604 static gboolean
605 gtk_bubble_window_key_press (GtkWidget   *widget,
606                              GdkEventKey *event)
607 {
608   if (event->keyval == GDK_KEY_Escape)
609     {
610       _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
611       return GDK_EVENT_STOP;
612     }
613
614   return GDK_EVENT_PROPAGATE;
615 }
616
617 static gboolean
618 gtk_bubble_window_grab_broken (GtkWidget          *widget,
619                                GdkEventGrabBroken *grab_broken)
620 {
621   GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
622   GtkBubbleWindowPrivate *priv;
623   GdkDevice *event_device;
624
625   priv = window->priv;
626   event_device = gdk_event_get_device ((GdkEvent *) grab_broken);
627
628   if (event_device == priv->device ||
629       event_device == gdk_device_get_associated_device (priv->device))
630     _gtk_bubble_window_ungrab (window);
631
632   return FALSE;
633 }
634
635 static void
636 gtk_bubble_window_grab_notify (GtkWidget *widget,
637                                gboolean   was_grabbed)
638 {
639   GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
640   GtkBubbleWindowPrivate *priv;
641
642   priv = window->priv;
643
644   if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device))
645     _gtk_bubble_window_ungrab (window);
646 }
647
648 static void
649 gtk_bubble_window_screen_changed (GtkWidget *widget,
650                                   GdkScreen *previous_screen)
651 {
652   GdkScreen *screen;
653   GdkVisual *visual;
654
655   screen = gtk_widget_get_screen (widget);
656   visual = gdk_screen_get_rgba_visual (screen);
657
658   if (visual)
659     gtk_widget_set_visual (widget, visual);
660 }
661
662 static void
663 _gtk_bubble_window_class_init (GtkBubbleWindowClass *klass)
664 {
665   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
666   GObjectClass *object_class = G_OBJECT_CLASS (klass);
667
668   object_class->constructor = gtk_bubble_window_constructor;
669   object_class->set_property = gtk_bubble_window_set_property;
670   object_class->get_property = gtk_bubble_window_get_property;
671   object_class->finalize = gtk_bubble_window_finalize;
672
673   widget_class->get_preferred_width = gtk_bubble_window_get_preferred_width;
674   widget_class->get_preferred_height = gtk_bubble_window_get_preferred_height;
675   widget_class->size_allocate = gtk_bubble_window_size_allocate;
676   widget_class->draw = gtk_bubble_window_draw;
677   widget_class->button_press_event = gtk_bubble_window_button_press;
678   widget_class->key_press_event = gtk_bubble_window_key_press;
679   widget_class->grab_broken_event = gtk_bubble_window_grab_broken;
680   widget_class->grab_notify = gtk_bubble_window_grab_notify;
681   widget_class->screen_changed = gtk_bubble_window_screen_changed;
682
683   g_object_class_install_property (object_class,
684                                    PROP_RELATIVE_TO,
685                                    g_param_spec_object ("relative-to",
686                                                         P_("Relative to"),
687                                                         P_("Window the bubble window points to"),
688                                                         GDK_TYPE_WINDOW,
689                                                         GTK_PARAM_READWRITE));
690   g_object_class_install_property (object_class,
691                                    PROP_POINTING_TO,
692                                    g_param_spec_boxed ("pointing-to",
693                                                        P_("Pointing to"),
694                                                        P_("Rectangle the bubble window points to"),
695                                                        CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
696                                                        GTK_PARAM_READWRITE));
697   g_object_class_install_property (object_class,
698                                    PROP_POSITION,
699                                    g_param_spec_enum ("position",
700                                                       P_("Position"),
701                                                       P_("Position to place the bubble window"),
702                                                       GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
703                                                       GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
704
705   g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate));
706 }
707
708 static void
709 gtk_bubble_window_update_relative_to (GtkBubbleWindow *window,
710                                       GdkWindow       *relative_to)
711 {
712   GtkBubbleWindowPrivate *priv;
713
714   priv = window->priv;
715
716   if (priv->relative_to == relative_to)
717     return;
718
719   if (priv->relative_to)
720     g_object_unref (priv->relative_to);
721
722   priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL;
723   g_object_notify (G_OBJECT (window), "relative-to");
724 }
725
726 static void
727 gtk_bubble_window_update_pointing_to (GtkBubbleWindow       *window,
728                                       cairo_rectangle_int_t *pointing_to)
729 {
730   GtkBubbleWindowPrivate *priv;
731
732   priv = window->priv;
733   priv->pointing_to = *pointing_to;
734   priv->has_pointing_to = TRUE;
735   g_object_notify (G_OBJECT (window), "pointing-to");
736 }
737
738 static void
739 gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window,
740                                              GtkPositionType  position)
741 {
742   GtkBubbleWindowPrivate *priv;
743
744   priv = window->priv;
745   priv->preferred_position = position;
746   g_object_notify (G_OBJECT (window), "position");
747 }
748
749 /*
750  * gtk_bubble_window_new:
751  *
752  * Returns a newly created #GtkBubblewindow
753  *
754  * Returns: a new #GtkBubbleWindow
755  *
756  * Since: 3.8
757  */
758 GtkWidget *
759 _gtk_bubble_window_new (void)
760 {
761   return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL);
762 }
763
764 /*
765  * gtk_bubble_window_set_relative_to:
766  * @window: a #GtkBubbleWindow
767  * @relative_to: a #GdkWindow
768  *
769  * Sets the #GdkWindow to act as the origin of coordinates of
770  * @window, or %NULL to use the root window. See
771  * gtk_bubble_window_set_pointing_to()
772  *
773  * If @window is currently visible, it will be moved to reflect
774  * this change.
775  *
776  * Since: 3.8
777  */
778 void
779 _gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
780                                     GdkWindow       *relative_to)
781 {
782   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
783   g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
784
785   gtk_bubble_window_update_relative_to (window, relative_to);
786
787   if (gtk_widget_get_visible (GTK_WIDGET (window)))
788     gtk_bubble_window_update_position (window);
789 }
790
791 /*
792  * gtk_bubble_window_get_relative_to:
793  * @window: a #GtkBubbleWindow
794  *
795  * Returns the #GdkWindow used as the origin of coordinates.
796  * If @window is currently visible, it will be moved to reflect
797  * this change.
798  *
799  * Returns: the #GdkWindow @window is placed upon
800  *
801  * Since: 3.8
802  */
803 GdkWindow *
804 _gtk_bubble_window_get_relative_to (GtkBubbleWindow *window)
805 {
806   GtkBubbleWindowPrivate *priv;
807
808   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL);
809
810   priv = window->priv;
811
812   return priv->relative_to;
813 }
814
815 /*
816  * gtk_bubble_window_set_pointing_to:
817  * @window: a #GtkBubbleWindow
818  * @rect: rectangle to point to
819  *
820  * Sets the rectangle that @window will point to, the coordinates
821  * of this rectangle are relative to the #GdkWindow set through
822  * gtk_bubble_window_set_relative_to().
823  *
824  * Since: 3.8
825  */
826 void
827 _gtk_bubble_window_set_pointing_to (GtkBubbleWindow       *window,
828                                     cairo_rectangle_int_t *rect)
829 {
830   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
831   g_return_if_fail (rect != NULL);
832
833   gtk_bubble_window_update_pointing_to (window, rect);
834
835   if (gtk_widget_get_visible (GTK_WIDGET (window)))
836     gtk_bubble_window_update_position (window);
837 }
838
839 /*
840  * gtk_bubble_window_get_pointing_to:
841  * @window: a #GtkBubbleWindow
842  * @rect: (out): location to store the rectangle
843  *
844  * If a rectangle to point to is set, this function will return
845  * %TRUE and fill in @rect with the rectangle @window is currently
846  * pointing to.
847  *
848  * Returns: %TRUE if a rectangle is set
849  *
850  * Since: 3.8
851  */
852 gboolean
853 _gtk_bubble_window_get_pointing_to (GtkBubbleWindow       *window,
854                                     cairo_rectangle_int_t *rect)
855 {
856   GtkBubbleWindowPrivate *priv;
857
858   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
859
860   priv = window->priv;
861
862   if (rect)
863     *rect = priv->pointing_to;
864
865   return priv->has_pointing_to;
866 }
867
868 /*
869  * gtk_bubble_window_set_position:
870  * @window: a #GtkBubbleWindow
871  * @position: preferred bubble position
872  *
873  * Sets the preferred position for @window to appear.
874  * If @window is currently visible, it will be moved to reflect
875  * this change.
876  *
877  * <note>
878  *   This preference will be respected where possible, although
879  *   on lack of space (eg. if close to the screen edges), the
880  *   #GtkBubbleWindow may choose to appear on the opposite side
881  * </note>
882  *
883  * Since: 3.8
884  */
885 void
886 _gtk_bubble_window_set_position (GtkBubbleWindow *window,
887                                  GtkPositionType  position)
888 {
889   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
890   g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
891
892   gtk_bubble_window_update_preferred_position (window, position);
893
894   if (gtk_widget_get_visible (GTK_WIDGET (window)))
895     gtk_bubble_window_update_position (window);
896 }
897
898 /*
899  * gtk_bubble_window_get_position:
900  * @window: a #GtkBubbleWindow
901  *
902  * Returns the preferred position to place @window
903  *
904  * Returns: The preferred position
905  *
906  * Since: 3.8
907  */
908 GtkPositionType
909 _gtk_bubble_window_get_position (GtkBubbleWindow *window)
910 {
911   GtkBubbleWindowPrivate *priv;
912
913   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP);
914
915   priv = window->priv;
916
917   return priv->preferred_position;
918 }
919
920 /*
921  * gtk_bubble_window_popup:
922  * @window: a #GtkBubbleWindow
923  * @relative_to: #GdkWindow to position upon
924  * @pointing_to: rectangle to point to, in @relative_to coordinates
925  * @position: preferred position for @window
926  *
927  * This function sets atomically all #GtkBubbleWindow position
928  * parameters, and shows/updates @window
929  *
930  * Since: 3.8
931  */
932 void
933 _gtk_bubble_window_popup (GtkBubbleWindow       *window,
934                           GdkWindow             *relative_to,
935                           cairo_rectangle_int_t *pointing_to,
936                           GtkPositionType        position)
937 {
938   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
939   g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
940   g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
941   g_return_if_fail (pointing_to != NULL);
942
943   gtk_bubble_window_update_preferred_position (window, position);
944   gtk_bubble_window_update_relative_to (window, relative_to);
945   gtk_bubble_window_update_pointing_to (window, pointing_to);
946
947   if (!gtk_widget_get_visible (GTK_WIDGET (window)))
948     gtk_widget_show (GTK_WIDGET (window));
949
950   gtk_bubble_window_update_position (window);
951 }
952
953 /*
954  * gtk_bubble_window_popdown:
955  * @window: a #GtkBubbleWindow
956  *
957  * Removes the window from the screen
958  *
959  * <note>
960  *   If a grab was previously added through gtk_bubble_window_grab(),
961  *   the grab will be removed by this function.
962  * </note>
963  *
964  * Since: 3.8
965  */
966 void
967 _gtk_bubble_window_popdown (GtkBubbleWindow *window)
968 {
969   GtkBubbleWindowPrivate *priv = window->priv;
970
971   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
972
973   if (priv->grabbed)
974     _gtk_bubble_window_ungrab (window);
975
976   if (gtk_widget_get_visible (GTK_WIDGET (window)))
977     gtk_widget_hide (GTK_WIDGET (window));
978 }
979
980 /*
981  * gtk_bubble_window_grab:
982  * @window: a #GtkBubbleWindow
983  * @device: a master #GdkDevice
984  * @activate_time: timestamp to perform the grab
985  *
986  * This function performs GDK and GTK+ grabs on @device and
987  * its paired #GdkDevice. After this call all pointer/keyboard
988  * events will be handled by @window.
989  *
990  * Calling this also brings in a #GtkMenu alike behavior, clicking
991  * outside the #GtkBubbleWindow or pressing the Escape key will
992  * popdown the menu by default.
993  *
994  * <note>
995  *   If there was a previous grab, it will be undone before doing
996  *   the requested grab.
997  * </note>
998  *
999  * Returns: %TRUE if the grab was successful
1000  *
1001  * Since: 3.8
1002  */
1003 gboolean
1004 _gtk_bubble_window_grab (GtkBubbleWindow *window,
1005                          GdkDevice       *device,
1006                          guint32          activate_time)
1007 {
1008   GtkBubbleWindowPrivate *priv;
1009   GdkDevice *other_device;
1010   GdkWindow *grab_window;
1011   GdkGrabStatus status;
1012
1013   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
1014   g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
1015   g_return_val_if_fail (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER, FALSE);
1016
1017   priv = window->priv;
1018
1019   if (!priv->has_pointing_to ||
1020       gdk_window_is_destroyed (priv->relative_to))
1021     return FALSE;
1022
1023   if (priv->device)
1024     _gtk_bubble_window_ungrab (window);
1025
1026   gtk_widget_realize (GTK_WIDGET (window));
1027   grab_window = gtk_widget_get_window (GTK_WIDGET (window));
1028   other_device = gdk_device_get_associated_device (device);
1029
1030   status = gdk_device_grab (device, grab_window,
1031                             GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1032                             NULL, activate_time);
1033
1034   if (status == GDK_GRAB_SUCCESS)
1035     {
1036       status = gdk_device_grab (other_device, grab_window,
1037                                 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1038                                 NULL, activate_time);
1039
1040       /* Ungrab the first device on error */
1041       if (status != GDK_GRAB_SUCCESS)
1042         gdk_device_ungrab (device, activate_time);
1043     }
1044
1045   if (status == GDK_GRAB_SUCCESS)
1046     {
1047       gtk_device_grab_add (GTK_WIDGET (window), device, TRUE);
1048       priv->device = device;
1049     }
1050
1051   return status == GDK_GRAB_SUCCESS;
1052 }
1053
1054 /*
1055  * gtk_bubble_window_ungrab:
1056  * @window: a #GtkBubbleWindow
1057  *
1058  * This functions undoes a grab added through gtk_bubble_window_grab()
1059  * in this @window,
1060  *
1061  * Since: 3.8
1062  */
1063 void
1064 _gtk_bubble_window_ungrab (GtkBubbleWindow *window)
1065 {
1066   GtkBubbleWindowPrivate *priv;
1067
1068   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1069
1070   priv = window->priv;
1071
1072   if (!priv->device)
1073     return;
1074
1075   gdk_device_ungrab (priv->device, GDK_CURRENT_TIME);
1076   gdk_device_ungrab (gdk_device_get_associated_device (priv->device),
1077                      GDK_CURRENT_TIME);
1078   gtk_device_grab_remove (GTK_WIDGET (window), priv->device);
1079   priv->device = NULL;
1080 }