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