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