]> Pileus Git - ~andy/gtk/blob - gtk/gtkbubblewindow.c
f2ef2ecedcc5423fd3e943726db652651392c50d
[~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_get_gap_coords (GtkBubbleWindow *window,
220                                   gint            *initial_x_out,
221                                   gint            *initial_y_out,
222                                   gint            *tip_x_out,
223                                   gint            *tip_y_out,
224                                   gint            *final_x_out,
225                                   gint            *final_y_out,
226                                   GtkPositionType *gap_side_out)
227 {
228   GtkBubbleWindowPrivate *priv = window->priv;
229   gint base, tip, x, y;
230   gint initial_x, initial_y;
231   gint tip_x, tip_y;
232   gint final_x, final_y;
233   GtkPositionType gap_side;
234   GtkAllocation allocation;
235
236   gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL);
237   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
238
239   base = tip = 0;
240   gap_side = GTK_POS_LEFT;
241
242   if (priv->final_position == GTK_POS_BOTTOM ||
243       priv->final_position == GTK_POS_RIGHT)
244     {
245       base = TAIL_HEIGHT;
246       tip = 0;
247
248       gap_side = (priv->final_position == GTK_POS_BOTTOM) ? GTK_POS_TOP : GTK_POS_LEFT;
249     }
250   else if (priv->final_position == GTK_POS_TOP)
251     {
252       base = allocation.height - TAIL_HEIGHT;
253       tip = allocation.height;
254       gap_side = GTK_POS_BOTTOM;
255     }
256   else if (priv->final_position == GTK_POS_LEFT)
257     {
258       base = allocation.width - TAIL_HEIGHT;
259       tip = allocation.width;
260       gap_side = GTK_POS_RIGHT;
261     }
262
263   if (POS_IS_VERTICAL (priv->final_position))
264     {
265       initial_x = CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2,
266                          0, allocation.width - TAIL_GAP_WIDTH);
267       initial_y = base;
268
269       tip_x = CLAMP (x - priv->win_x, 0, allocation.width);
270       tip_y = tip;
271
272       final_x = CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2,
273                        TAIL_GAP_WIDTH, allocation.width);
274       final_y = base;
275     }
276   else
277     {
278       initial_x = base;
279       initial_y = CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2,
280                          0, allocation.height - TAIL_GAP_WIDTH);
281
282       tip_x = tip;
283       tip_y = CLAMP (y - priv->win_y, 0, allocation.height);
284
285       final_x = base;
286       final_y = CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2,
287                        TAIL_GAP_WIDTH, allocation.height);
288     }
289
290   if (initial_x_out)
291     *initial_x_out = initial_x;
292   if (initial_y_out)
293     *initial_y_out = initial_y;
294
295   if (tip_x_out)
296     *tip_x_out = tip_x;
297   if (tip_y_out)
298     *tip_y_out = tip_y;
299
300   if (final_x_out)
301     *final_x_out = final_x;
302   if (final_y_out)
303     *final_y_out = final_y;
304
305   if (gap_side_out)
306     *gap_side_out = gap_side;
307 }
308
309 static void
310 gtk_bubble_window_get_rect_coords (GtkBubbleWindow *window,
311                                    gint            *x1_out,
312                                    gint            *y1_out,
313                                    gint            *x2_out,
314                                    gint            *y2_out)
315 {
316   GtkBubbleWindowPrivate *priv = window->priv;
317   gint x1, x2, y1, y2;
318   GtkAllocation allocation;
319
320   x1 = y1 = x2 = y2 = 0;
321   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
322
323   if (priv->final_position == GTK_POS_TOP)
324     {
325       x1 = 0;
326       y1 = 0;
327       x2 = allocation.width;
328       y2 = allocation.height - TAIL_HEIGHT;
329     }
330   else if (priv->final_position == GTK_POS_BOTTOM)
331     {
332       x1 = 0;
333       y1 = TAIL_HEIGHT;
334       x2 = allocation.width;
335       y2 = allocation.height;
336     }
337   else if (priv->final_position == GTK_POS_LEFT)
338     {
339       x1 = 0;
340       y1 = 0;
341       x2 = allocation.width - TAIL_HEIGHT;
342       y2 = allocation.height;
343     }
344   else if (priv->final_position == GTK_POS_RIGHT)
345     {
346       x1 = TAIL_HEIGHT;
347       y1 = 0;
348       x2 = allocation.width;
349       y2 = allocation.height;
350     }
351
352   if (x1_out)
353     *x1_out = x1;
354   if (y1_out)
355     *y1_out = y1;
356   if (x2_out)
357     *x2_out = x2;
358   if (y2_out)
359     *y2_out = y2;
360 }
361
362 static void
363 gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window,
364                                    cairo_t         *cr)
365 {
366   gint initial_x, initial_y;
367   gint tip_x, tip_y;
368   gint final_x, final_y;
369
370   gtk_bubble_window_get_gap_coords (window,
371                                     &initial_x, &initial_y,
372                                     &tip_x, &tip_y,
373                                     &final_x, &final_y,
374                                     NULL);
375
376   cairo_move_to (cr, initial_x, initial_y);
377   cairo_line_to (cr, tip_x, tip_y);
378   cairo_line_to (cr, final_x, final_y);
379 }
380
381 static void
382 gtk_bubble_window_apply_border_path (GtkBubbleWindow *window,
383                                      cairo_t         *cr)
384 {
385   GtkBubbleWindowPrivate *priv;
386   GtkAllocation allocation;
387   gint x1, y1, x2, y2;
388
389   priv = window->priv;
390   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
391
392   gtk_bubble_window_apply_tail_path (window, cr);
393   gtk_bubble_window_get_rect_coords (window, &x1, &y1, &x2, &y2);
394
395   if (priv->final_position == GTK_POS_TOP)
396     {
397       cairo_line_to (cr, x2, y2);
398       cairo_line_to (cr, x2, y1);
399       cairo_line_to (cr, x1, y1);
400       cairo_line_to (cr, x1, y2);
401     }
402   else if (priv->final_position == GTK_POS_BOTTOM)
403     {
404       cairo_line_to (cr, x2, y1);
405       cairo_line_to (cr, x2, y2);
406       cairo_line_to (cr, x1, y2);
407       cairo_line_to (cr, x1, y1);
408     }
409   else if (priv->final_position == GTK_POS_LEFT)
410     {
411       cairo_line_to (cr, x2, y2);
412       cairo_line_to (cr, x1, y2);
413       cairo_line_to (cr, x1, y1);
414       cairo_line_to (cr, x2, y1);
415     }
416   else if (priv->final_position == GTK_POS_RIGHT)
417     {
418       cairo_line_to (cr, x1, y1);
419       cairo_line_to (cr, x2, y1);
420       cairo_line_to (cr, x2, y2);
421       cairo_line_to (cr, x1, y2);
422     }
423
424   cairo_close_path (cr);
425 }
426
427 static void
428 gtk_bubble_window_update_shape (GtkBubbleWindow *window)
429 {
430   cairo_surface_t *surface;
431   cairo_region_t *region;
432   GdkWindow *win;
433   cairo_t *cr;
434
435   win = gtk_widget_get_window (GTK_WIDGET (window));
436   surface =
437     gdk_window_create_similar_surface (win,
438                                        CAIRO_CONTENT_COLOR_ALPHA,
439                                        gdk_window_get_width (win),
440                                        gdk_window_get_height (win));
441
442   cr = cairo_create (surface);
443   gtk_bubble_window_apply_border_path (window, cr);
444   cairo_fill (cr);
445   cairo_destroy (cr);
446
447   region = gdk_cairo_region_create_from_surface (surface);
448   cairo_surface_destroy (surface);
449
450   if (!gtk_widget_is_composited (GTK_WIDGET (window)))
451     gtk_widget_shape_combine_region (GTK_WIDGET (window), region);
452
453   gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region);
454   cairo_region_destroy (region);
455 }
456
457 static void
458 gtk_bubble_window_update_position (GtkBubbleWindow *window)
459 {
460   GtkBubbleWindowPrivate *priv;
461   cairo_rectangle_int_t rect;
462   GtkAllocation allocation;
463   gint win_x, win_y, x, y;
464   GdkScreen *screen;
465
466   priv = window->priv;
467   screen = gtk_widget_get_screen (GTK_WIDGET (window));
468   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
469   priv->final_position = priv->preferred_position;
470   rect = priv->pointing_to;
471
472   gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect);
473
474   /* Check whether there's enough room on the
475    * preferred side, move to the opposite one if not.
476    */
477   if (priv->preferred_position == GTK_POS_TOP && rect.y < allocation.height)
478     priv->final_position = GTK_POS_BOTTOM;
479   else if (priv->preferred_position == GTK_POS_BOTTOM &&
480            rect.y > gdk_screen_get_height (screen) - allocation.height)
481     priv->final_position = GTK_POS_TOP;
482   else if (priv->preferred_position == GTK_POS_LEFT && rect.x < allocation.width)
483     priv->final_position = GTK_POS_RIGHT;
484   else if (priv->preferred_position == GTK_POS_RIGHT &&
485            rect.x > gdk_screen_get_width (screen) - allocation.width)
486     priv->final_position = GTK_POS_LEFT;
487
488   if (POS_IS_VERTICAL (priv->final_position))
489     {
490       win_x = CLAMP (x - allocation.width / 2,
491                      0, gdk_screen_get_width (screen) - allocation.width);
492       win_y = y;
493
494       if (priv->final_position == GTK_POS_TOP)
495         win_y -= allocation.height;
496     }
497   else
498     {
499       win_y = CLAMP (y - allocation.height / 2,
500                      0, gdk_screen_get_height (screen) - allocation.height);
501       win_x = x;
502
503       if (priv->final_position == GTK_POS_LEFT)
504         win_x -= allocation.width;
505
506     }
507
508   priv->win_x = win_x;
509   priv->win_y = win_y;
510   gtk_window_move (GTK_WINDOW (window), win_x, win_y);
511
512   gtk_widget_queue_resize (GTK_WIDGET (window));
513 }
514
515 static gboolean
516 gtk_bubble_window_draw (GtkWidget *widget,
517                         cairo_t   *cr)
518 {
519   GtkStyleContext *context;
520   GtkAllocation allocation;
521   GtkWidget *child;
522   GtkBorder border;
523   GdkRGBA border_color;
524   gint rect_x1, rect_x2, rect_y1, rect_y2;
525   gint initial_x, initial_y, final_x, final_y;
526   gint gap_start, gap_end;
527   GtkPositionType gap_side;
528   GtkStateFlags state;
529
530   context = gtk_widget_get_style_context (widget);
531   state = gtk_widget_get_state_flags (widget);
532   gtk_widget_get_allocation (widget, &allocation);
533
534   if (gtk_widget_is_composited (widget))
535     {
536       cairo_save (cr);
537       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
538       cairo_set_source_rgba (cr, 0, 0, 0, 0);
539       cairo_paint (cr);
540       cairo_restore (cr);
541     }
542
543   gtk_bubble_window_get_rect_coords (GTK_BUBBLE_WINDOW (widget),
544                                      &rect_x1, &rect_y1,
545                                      &rect_x2, &rect_y2);
546
547   /* Render the rect background */
548   gtk_render_background (context, cr,
549                          rect_x1, rect_y1,
550                          rect_x2 - rect_x1, rect_y2 - rect_y1);
551
552   gtk_bubble_window_get_gap_coords (GTK_BUBBLE_WINDOW (widget),
553                                     &initial_x, &initial_y,
554                                     NULL, NULL,
555                                     &final_x, &final_y,
556                                     &gap_side);
557
558   if (POS_IS_VERTICAL (gap_side))
559     {
560       gap_start = initial_x;
561       gap_end = final_x;
562     }
563   else
564     {
565       gap_start = initial_y;
566       gap_end = final_y;
567     }
568
569   /* Now render the frame, without the gap for the arrow tip */
570   gtk_render_frame_gap (context, cr,
571                         rect_x1, rect_y1,
572                         rect_x2 - rect_x1, rect_y2 - rect_y1,
573                         gap_side,
574                         gap_start, gap_end);
575
576   /* Clip to the arrow shape */
577   cairo_save (cr);
578
579   gtk_bubble_window_apply_tail_path (GTK_BUBBLE_WINDOW (widget), cr);
580   cairo_clip (cr);
581
582   /* Render the arrow background */
583   gtk_render_background (context, cr,
584                          0, 0,
585                          allocation.width, allocation.height);
586
587   /* Render the border of the arrow tip */
588   gtk_style_context_get_border (context, state, &border);
589
590   if (border.bottom > 0)
591     {
592       gtk_style_context_get_border_color (context, state, &border_color);
593       gtk_bubble_window_apply_tail_path (GTK_BUBBLE_WINDOW (widget), cr);
594       gdk_cairo_set_source_rgba (cr, &border_color);
595
596       cairo_set_line_width (cr, border.bottom);
597       cairo_stroke (cr);
598     }
599
600   /* We're done */
601   cairo_restore (cr);
602
603   child = gtk_bin_get_child (GTK_BIN (widget));
604
605   if (child)
606     gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
607
608   return TRUE;
609 }
610
611 static void
612 get_padding_and_border (GtkWidget *widget,
613                         GtkBorder *border)
614 {
615   GtkStyleContext *context;
616   GtkStateFlags state;
617   GtkBorder tmp;
618
619   context = gtk_widget_get_style_context (widget);
620   state = gtk_widget_get_state_flags (widget);
621
622   gtk_style_context_get_padding (context, state, border);
623   gtk_style_context_get_border (context, state, &tmp);
624   border->top += tmp.top;
625   border->right += tmp.right;
626   border->bottom += tmp.bottom;
627   border->left += tmp.left;
628 }
629
630 static void
631 gtk_bubble_window_get_preferred_width (GtkWidget *widget,
632                                        gint      *minimum_width,
633                                        gint      *natural_width)
634 {
635   GtkBubbleWindowPrivate *priv;
636   GtkWidget *child;
637   gint min, nat;
638   GtkBorder border;
639
640   priv = GTK_BUBBLE_WINDOW (widget)->priv;
641   child = gtk_bin_get_child (GTK_BIN (widget));
642   min = nat = 0;
643
644   if (child)
645     gtk_widget_get_preferred_width (child, &min, &nat);
646
647   get_padding_and_border (widget, &border);
648   min += border.left + border.right;
649   nat += border.left + border.right;
650
651   if (!POS_IS_VERTICAL (priv->final_position))
652     {
653       min += TAIL_HEIGHT;
654       nat += TAIL_HEIGHT;
655     }
656
657   if (minimum_width)
658     *minimum_width = MAX (min, TAIL_GAP_WIDTH);
659
660   if (natural_width)
661     *natural_width = MAX (nat, TAIL_GAP_WIDTH);
662 }
663
664 static void
665 gtk_bubble_window_get_preferred_height (GtkWidget *widget,
666                                         gint      *minimum_height,
667                                         gint      *natural_height)
668 {
669   GtkBubbleWindowPrivate *priv;
670   GtkWidget *child;
671   gint min, nat;
672   GtkBorder border;
673
674   priv = GTK_BUBBLE_WINDOW (widget)->priv;
675   child = gtk_bin_get_child (GTK_BIN (widget));
676   min = nat = 0;
677
678   if (child)
679     gtk_widget_get_preferred_height (child, &min, &nat);
680
681   get_padding_and_border (widget, &border);
682   min += border.top + border.bottom;
683   nat += border.top + border.bottom;
684
685   if (POS_IS_VERTICAL (priv->final_position))
686     {
687       min += TAIL_HEIGHT;
688       nat += TAIL_HEIGHT;
689     }
690
691   if (minimum_height)
692     *minimum_height = MAX (min, TAIL_GAP_WIDTH);
693
694   if (natural_height)
695     *natural_height = MAX (nat, TAIL_GAP_WIDTH);
696 }
697
698 static void
699 gtk_bubble_window_size_allocate (GtkWidget     *widget,
700                                  GtkAllocation *allocation)
701 {
702   GtkBubbleWindowPrivate *priv;
703   GtkWidget *child;
704
705   priv = GTK_BUBBLE_WINDOW (widget)->priv;
706   gtk_widget_set_allocation (widget, allocation);
707   child = gtk_bin_get_child (GTK_BIN (widget));
708
709   if (child)
710     {
711       GtkAllocation child_alloc;
712       GtkBorder border;
713
714       get_padding_and_border (widget, &border);
715
716       child_alloc.x = border.left;
717       child_alloc.y = border.top;
718       child_alloc.width = allocation->width - border.left - border.right;
719       child_alloc.height = allocation->height - border.top - border.bottom;
720
721       if (POS_IS_VERTICAL (priv->final_position))
722         child_alloc.height -= TAIL_HEIGHT;
723       else
724         child_alloc.width -= TAIL_HEIGHT;
725
726       if (priv->final_position == GTK_POS_BOTTOM)
727         child_alloc.y += TAIL_HEIGHT;
728       else if (priv->final_position == GTK_POS_RIGHT)
729         child_alloc.x += TAIL_HEIGHT;
730
731       gtk_widget_size_allocate (child, &child_alloc);
732     }
733
734   if (gtk_widget_get_realized (widget))
735     gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget));
736
737   if (gtk_widget_get_visible (widget))
738     gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget));
739 }
740
741 static gboolean
742 gtk_bubble_window_button_press (GtkWidget      *widget,
743                                 GdkEventButton *event)
744 {
745   GtkWidget *child;
746
747   child = gtk_bin_get_child (GTK_BIN (widget));
748
749   if (child && event->window == gtk_widget_get_window (widget))
750     {
751       GtkAllocation child_alloc;
752
753       gtk_widget_get_allocation (child, &child_alloc);
754
755       if (event->x < child_alloc.x ||
756           event->x > child_alloc.x + child_alloc.width ||
757           event->y < child_alloc.y ||
758           event->y > child_alloc.y + child_alloc.height)
759         _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
760     }
761   else
762     _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
763
764   return GDK_EVENT_PROPAGATE;
765 }
766
767 static gboolean
768 gtk_bubble_window_key_press (GtkWidget   *widget,
769                              GdkEventKey *event)
770 {
771   if (event->keyval == GDK_KEY_Escape)
772     {
773       _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
774       return GDK_EVENT_STOP;
775     }
776
777   return GDK_EVENT_PROPAGATE;
778 }
779
780 static gboolean
781 gtk_bubble_window_grab_broken (GtkWidget          *widget,
782                                GdkEventGrabBroken *grab_broken)
783 {
784   GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
785   GtkBubbleWindowPrivate *priv;
786   GdkDevice *event_device;
787
788   priv = window->priv;
789   event_device = gdk_event_get_device ((GdkEvent *) grab_broken);
790
791   if (event_device == priv->device ||
792       event_device == gdk_device_get_associated_device (priv->device))
793     _gtk_bubble_window_ungrab (window);
794
795   return FALSE;
796 }
797
798 static void
799 gtk_bubble_window_grab_notify (GtkWidget *widget,
800                                gboolean   was_grabbed)
801 {
802   GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
803   GtkBubbleWindowPrivate *priv;
804
805   priv = window->priv;
806
807   if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device))
808     _gtk_bubble_window_ungrab (window);
809 }
810
811 static void
812 gtk_bubble_window_screen_changed (GtkWidget *widget,
813                                   GdkScreen *previous_screen)
814 {
815   GdkScreen *screen;
816   GdkVisual *visual;
817
818   screen = gtk_widget_get_screen (widget);
819   visual = gdk_screen_get_rgba_visual (screen);
820
821   if (visual)
822     gtk_widget_set_visual (widget, visual);
823 }
824
825 static void
826 _gtk_bubble_window_class_init (GtkBubbleWindowClass *klass)
827 {
828   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
829   GObjectClass *object_class = G_OBJECT_CLASS (klass);
830
831   object_class->constructor = gtk_bubble_window_constructor;
832   object_class->set_property = gtk_bubble_window_set_property;
833   object_class->get_property = gtk_bubble_window_get_property;
834   object_class->finalize = gtk_bubble_window_finalize;
835
836   widget_class->get_preferred_width = gtk_bubble_window_get_preferred_width;
837   widget_class->get_preferred_height = gtk_bubble_window_get_preferred_height;
838   widget_class->size_allocate = gtk_bubble_window_size_allocate;
839   widget_class->draw = gtk_bubble_window_draw;
840   widget_class->button_press_event = gtk_bubble_window_button_press;
841   widget_class->key_press_event = gtk_bubble_window_key_press;
842   widget_class->grab_broken_event = gtk_bubble_window_grab_broken;
843   widget_class->grab_notify = gtk_bubble_window_grab_notify;
844   widget_class->screen_changed = gtk_bubble_window_screen_changed;
845
846   g_object_class_install_property (object_class,
847                                    PROP_RELATIVE_TO,
848                                    g_param_spec_object ("relative-to",
849                                                         P_("Relative to"),
850                                                         P_("Window the bubble window points to"),
851                                                         GDK_TYPE_WINDOW,
852                                                         GTK_PARAM_READWRITE));
853   g_object_class_install_property (object_class,
854                                    PROP_POINTING_TO,
855                                    g_param_spec_boxed ("pointing-to",
856                                                        P_("Pointing to"),
857                                                        P_("Rectangle the bubble window points to"),
858                                                        CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
859                                                        GTK_PARAM_READWRITE));
860   g_object_class_install_property (object_class,
861                                    PROP_POSITION,
862                                    g_param_spec_enum ("position",
863                                                       P_("Position"),
864                                                       P_("Position to place the bubble window"),
865                                                       GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
866                                                       GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
867
868   g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate));
869 }
870
871 static void
872 gtk_bubble_window_update_relative_to (GtkBubbleWindow *window,
873                                       GdkWindow       *relative_to)
874 {
875   GtkBubbleWindowPrivate *priv;
876
877   priv = window->priv;
878
879   if (priv->relative_to == relative_to)
880     return;
881
882   if (priv->relative_to)
883     g_object_unref (priv->relative_to);
884
885   priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL;
886   g_object_notify (G_OBJECT (window), "relative-to");
887 }
888
889 static void
890 gtk_bubble_window_update_pointing_to (GtkBubbleWindow       *window,
891                                       cairo_rectangle_int_t *pointing_to)
892 {
893   GtkBubbleWindowPrivate *priv;
894
895   priv = window->priv;
896   priv->pointing_to = *pointing_to;
897   priv->has_pointing_to = TRUE;
898   g_object_notify (G_OBJECT (window), "pointing-to");
899 }
900
901 static void
902 gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window,
903                                              GtkPositionType  position)
904 {
905   GtkBubbleWindowPrivate *priv;
906
907   priv = window->priv;
908   priv->preferred_position = position;
909   g_object_notify (G_OBJECT (window), "position");
910 }
911
912 /*
913  * gtk_bubble_window_new:
914  *
915  * Returns a newly created #GtkBubblewindow
916  *
917  * Returns: a new #GtkBubbleWindow
918  *
919  * Since: 3.8
920  */
921 GtkWidget *
922 _gtk_bubble_window_new (void)
923 {
924   return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL);
925 }
926
927 /*
928  * gtk_bubble_window_set_relative_to:
929  * @window: a #GtkBubbleWindow
930  * @relative_to: a #GdkWindow
931  *
932  * Sets the #GdkWindow to act as the origin of coordinates of
933  * @window, or %NULL to use the root window. See
934  * gtk_bubble_window_set_pointing_to()
935  *
936  * If @window is currently visible, it will be moved to reflect
937  * this change.
938  *
939  * Since: 3.8
940  */
941 void
942 _gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
943                                     GdkWindow       *relative_to)
944 {
945   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
946   g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
947
948   gtk_bubble_window_update_relative_to (window, relative_to);
949
950   if (gtk_widget_get_visible (GTK_WIDGET (window)))
951     gtk_bubble_window_update_position (window);
952 }
953
954 /*
955  * gtk_bubble_window_get_relative_to:
956  * @window: a #GtkBubbleWindow
957  *
958  * Returns the #GdkWindow used as the origin of coordinates.
959  * If @window is currently visible, it will be moved to reflect
960  * this change.
961  *
962  * Returns: the #GdkWindow @window is placed upon
963  *
964  * Since: 3.8
965  */
966 GdkWindow *
967 _gtk_bubble_window_get_relative_to (GtkBubbleWindow *window)
968 {
969   GtkBubbleWindowPrivate *priv;
970
971   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL);
972
973   priv = window->priv;
974
975   return priv->relative_to;
976 }
977
978 /*
979  * gtk_bubble_window_set_pointing_to:
980  * @window: a #GtkBubbleWindow
981  * @rect: rectangle to point to
982  *
983  * Sets the rectangle that @window will point to, the coordinates
984  * of this rectangle are relative to the #GdkWindow set through
985  * gtk_bubble_window_set_relative_to().
986  *
987  * Since: 3.8
988  */
989 void
990 _gtk_bubble_window_set_pointing_to (GtkBubbleWindow       *window,
991                                     cairo_rectangle_int_t *rect)
992 {
993   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
994   g_return_if_fail (rect != NULL);
995
996   gtk_bubble_window_update_pointing_to (window, rect);
997
998   if (gtk_widget_get_visible (GTK_WIDGET (window)))
999     gtk_bubble_window_update_position (window);
1000 }
1001
1002 /*
1003  * gtk_bubble_window_get_pointing_to:
1004  * @window: a #GtkBubbleWindow
1005  * @rect: (out): location to store the rectangle
1006  *
1007  * If a rectangle to point to is set, this function will return
1008  * %TRUE and fill in @rect with the rectangle @window is currently
1009  * pointing to.
1010  *
1011  * Returns: %TRUE if a rectangle is set
1012  *
1013  * Since: 3.8
1014  */
1015 gboolean
1016 _gtk_bubble_window_get_pointing_to (GtkBubbleWindow       *window,
1017                                     cairo_rectangle_int_t *rect)
1018 {
1019   GtkBubbleWindowPrivate *priv;
1020
1021   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
1022
1023   priv = window->priv;
1024
1025   if (rect)
1026     *rect = priv->pointing_to;
1027
1028   return priv->has_pointing_to;
1029 }
1030
1031 /*
1032  * gtk_bubble_window_set_position:
1033  * @window: a #GtkBubbleWindow
1034  * @position: preferred bubble position
1035  *
1036  * Sets the preferred position for @window to appear.
1037  * If @window is currently visible, it will be moved to reflect
1038  * this change.
1039  *
1040  * <note>
1041  *   This preference will be respected where possible, although
1042  *   on lack of space (eg. if close to the screen edges), the
1043  *   #GtkBubbleWindow may choose to appear on the opposite side
1044  * </note>
1045  *
1046  * Since: 3.8
1047  */
1048 void
1049 _gtk_bubble_window_set_position (GtkBubbleWindow *window,
1050                                  GtkPositionType  position)
1051 {
1052   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1053   g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
1054
1055   gtk_bubble_window_update_preferred_position (window, position);
1056
1057   if (gtk_widget_get_visible (GTK_WIDGET (window)))
1058     gtk_bubble_window_update_position (window);
1059 }
1060
1061 /*
1062  * gtk_bubble_window_get_position:
1063  * @window: a #GtkBubbleWindow
1064  *
1065  * Returns the preferred position to place @window
1066  *
1067  * Returns: The preferred position
1068  *
1069  * Since: 3.8
1070  */
1071 GtkPositionType
1072 _gtk_bubble_window_get_position (GtkBubbleWindow *window)
1073 {
1074   GtkBubbleWindowPrivate *priv;
1075
1076   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP);
1077
1078   priv = window->priv;
1079
1080   return priv->preferred_position;
1081 }
1082
1083 /*
1084  * gtk_bubble_window_popup:
1085  * @window: a #GtkBubbleWindow
1086  * @relative_to: #GdkWindow to position upon
1087  * @pointing_to: rectangle to point to, in @relative_to coordinates
1088  * @position: preferred position for @window
1089  *
1090  * This function sets atomically all #GtkBubbleWindow position
1091  * parameters, and shows/updates @window
1092  *
1093  * Since: 3.8
1094  */
1095 void
1096 _gtk_bubble_window_popup (GtkBubbleWindow       *window,
1097                           GdkWindow             *relative_to,
1098                           cairo_rectangle_int_t *pointing_to,
1099                           GtkPositionType        position)
1100 {
1101   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1102   g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
1103   g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
1104   g_return_if_fail (pointing_to != NULL);
1105
1106   gtk_bubble_window_update_preferred_position (window, position);
1107   gtk_bubble_window_update_relative_to (window, relative_to);
1108   gtk_bubble_window_update_pointing_to (window, pointing_to);
1109
1110   if (!gtk_widget_get_visible (GTK_WIDGET (window)))
1111     gtk_widget_show (GTK_WIDGET (window));
1112
1113   gtk_bubble_window_update_position (window);
1114 }
1115
1116 /*
1117  * gtk_bubble_window_popdown:
1118  * @window: a #GtkBubbleWindow
1119  *
1120  * Removes the window from the screen
1121  *
1122  * <note>
1123  *   If a grab was previously added through gtk_bubble_window_grab(),
1124  *   the grab will be removed by this function.
1125  * </note>
1126  *
1127  * Since: 3.8
1128  */
1129 void
1130 _gtk_bubble_window_popdown (GtkBubbleWindow *window)
1131 {
1132   GtkBubbleWindowPrivate *priv = window->priv;
1133
1134   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1135
1136   if (priv->grabbed)
1137     _gtk_bubble_window_ungrab (window);
1138
1139   if (gtk_widget_get_visible (GTK_WIDGET (window)))
1140     gtk_widget_hide (GTK_WIDGET (window));
1141 }
1142
1143 /*
1144  * gtk_bubble_window_grab:
1145  * @window: a #GtkBubbleWindow
1146  * @device: a master #GdkDevice
1147  * @activate_time: timestamp to perform the grab
1148  *
1149  * This function performs GDK and GTK+ grabs on @device and
1150  * its paired #GdkDevice. After this call all pointer/keyboard
1151  * events will be handled by @window.
1152  *
1153  * Calling this also brings in a #GtkMenu alike behavior, clicking
1154  * outside the #GtkBubbleWindow or pressing the Escape key will
1155  * popdown the menu by default.
1156  *
1157  * <note>
1158  *   If there was a previous grab, it will be undone before doing
1159  *   the requested grab.
1160  * </note>
1161  *
1162  * Returns: %TRUE if the grab was successful
1163  *
1164  * Since: 3.8
1165  */
1166 gboolean
1167 _gtk_bubble_window_grab (GtkBubbleWindow *window,
1168                          GdkDevice       *device,
1169                          guint32          activate_time)
1170 {
1171   GtkBubbleWindowPrivate *priv;
1172   GdkDevice *other_device;
1173   GdkWindow *grab_window;
1174   GdkGrabStatus status;
1175
1176   g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
1177   g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
1178   g_return_val_if_fail (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER, FALSE);
1179
1180   priv = window->priv;
1181
1182   if (!priv->has_pointing_to ||
1183       gdk_window_is_destroyed (priv->relative_to))
1184     return FALSE;
1185
1186   if (priv->device)
1187     _gtk_bubble_window_ungrab (window);
1188
1189   gtk_widget_realize (GTK_WIDGET (window));
1190   grab_window = gtk_widget_get_window (GTK_WIDGET (window));
1191   other_device = gdk_device_get_associated_device (device);
1192
1193   status = gdk_device_grab (device, grab_window,
1194                             GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1195                             NULL, activate_time);
1196
1197   if (status == GDK_GRAB_SUCCESS)
1198     {
1199       status = gdk_device_grab (other_device, grab_window,
1200                                 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1201                                 NULL, activate_time);
1202
1203       /* Ungrab the first device on error */
1204       if (status != GDK_GRAB_SUCCESS)
1205         gdk_device_ungrab (device, activate_time);
1206     }
1207
1208   if (status == GDK_GRAB_SUCCESS)
1209     {
1210       gtk_device_grab_add (GTK_WIDGET (window), device, TRUE);
1211       priv->device = device;
1212     }
1213
1214   return status == GDK_GRAB_SUCCESS;
1215 }
1216
1217 /*
1218  * gtk_bubble_window_ungrab:
1219  * @window: a #GtkBubbleWindow
1220  *
1221  * This functions undoes a grab added through gtk_bubble_window_grab()
1222  * in this @window,
1223  *
1224  * Since: 3.8
1225  */
1226 void
1227 _gtk_bubble_window_ungrab (GtkBubbleWindow *window)
1228 {
1229   GtkBubbleWindowPrivate *priv;
1230
1231   g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1232
1233   priv = window->priv;
1234
1235   if (!priv->device)
1236     return;
1237
1238   gdk_device_ungrab (priv->device, GDK_CURRENT_TIME);
1239   gdk_device_ungrab (gdk_device_get_associated_device (priv->device),
1240                      GDK_CURRENT_TIME);
1241   gtk_device_grab_remove (GTK_WIDGET (window), priv->device);
1242   priv->device = NULL;
1243 }