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