1 /* GTK - The GIMP Toolkit
2 * Copyright © 2013 Carlos Garnacho <carlosg@gnome.org>
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.
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.
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/>.
19 * SECTION:gtkbubblewindow
20 * @Short_description: Bubble-like context window
21 * @Title: GtkBubbleWindow
23 * GtkBubbleWindow is a bubble-like context window, primarily mean for
24 * context-dependent helpers on touch interfaces.
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
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().
41 #include <cairo-gobject.h>
42 #include "gtkbubblewindow.h"
43 #include "gtkprivate.h"
46 #define TAIL_GAP_WIDTH 24
47 #define TAIL_HEIGHT 12
49 #define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
51 #define GRAB_EVENT_MASK \
52 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
53 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | \
54 GDK_POINTER_MOTION_MASK
56 typedef struct _GtkBubbleWindowPrivate GtkBubbleWindowPrivate;
64 struct _GtkBubbleWindowPrivate
67 GdkWindow *relative_to;
68 cairo_rectangle_int_t pointing_to;
71 guint has_pointing_to : 1;
73 guint preferred_position : 2;
74 guint final_position : 2;
77 G_DEFINE_TYPE (GtkBubbleWindow, gtk_bubble_window, GTK_TYPE_WINDOW)
80 gtk_bubble_window_init (GtkBubbleWindow *window)
86 widget = GTK_WIDGET (window);
87 window->_priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
88 GTK_TYPE_BUBBLE_WINDOW,
89 GtkBubbleWindowPrivate);
90 gtk_window_set_default_size (GTK_WINDOW (window),
91 TAIL_GAP_WIDTH, TAIL_GAP_WIDTH);
92 gtk_widget_set_app_paintable (widget, TRUE);
94 screen = gtk_widget_get_screen (widget);
95 visual = gdk_screen_get_rgba_visual (screen);
98 gtk_widget_set_visual (widget, visual);
102 gtk_bubble_window_constructor (GType type,
103 guint n_construct_properties,
104 GObjectConstructParam *construct_properties)
109 G_OBJECT_CLASS (gtk_bubble_window_parent_class)->constructor (type,
110 n_construct_properties,
111 construct_properties);
112 g_object_set (object, "type", GTK_WINDOW_POPUP, NULL);
118 gtk_bubble_window_set_property (GObject *object,
125 case PROP_RELATIVE_TO:
126 gtk_bubble_window_set_relative_to (GTK_BUBBLE_WINDOW (object),
127 g_value_get_object (value));
129 case PROP_POINTING_TO:
130 gtk_bubble_window_set_pointing_to (GTK_BUBBLE_WINDOW (object),
131 g_value_get_boxed (value));
134 gtk_bubble_window_set_position (GTK_BUBBLE_WINDOW (object),
135 g_value_get_enum (value));
138 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
143 gtk_bubble_window_get_property (GObject *object,
148 GtkBubbleWindowPrivate *priv = GTK_BUBBLE_WINDOW (object)->_priv;
152 case PROP_RELATIVE_TO:
153 g_value_set_object (value, priv->relative_to);
155 case PROP_POINTING_TO:
156 g_value_set_boxed (value, &priv->pointing_to);
159 g_value_set_enum (value, priv->preferred_position);
162 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
167 gtk_bubble_window_finalize (GObject *object)
169 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (object);
170 GtkBubbleWindowPrivate *priv = window->_priv;;
172 gtk_bubble_window_popdown (window);
174 if (priv->relative_to)
175 g_object_unref (priv->relative_to);
177 G_OBJECT_CLASS (gtk_bubble_window_parent_class)->finalize (object);
181 _gtk_bubble_window_get_pointed_to_coords (GtkBubbleWindow *window,
184 cairo_rectangle_int_t *root_rect)
186 GtkBubbleWindowPrivate *priv = window->_priv;
187 cairo_rectangle_int_t rect;
190 rect = priv->pointing_to;
191 screen = gtk_widget_get_screen (GTK_WIDGET (window));
193 if (priv->relative_to)
194 gdk_window_get_root_coords (priv->relative_to,
195 rect.x, rect.y, &rect.x, &rect.y);
197 if (POS_IS_VERTICAL (priv->final_position))
199 *x = CLAMP (rect.x + (rect.width / 2),
200 0, gdk_screen_get_width (screen));
203 if (priv->final_position == GTK_POS_BOTTOM)
208 *y = CLAMP (rect.y + (rect.height / 2),
209 0, gdk_screen_get_height (screen));
212 if (priv->final_position == GTK_POS_RIGHT)
221 _gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window,
223 GtkAllocation *allocation)
225 GtkBubbleWindowPrivate *priv = window->_priv;
226 gint base, tip, x, y;
228 _gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL);
230 if (priv->final_position == GTK_POS_BOTTOM ||
231 priv->final_position == GTK_POS_RIGHT)
236 else if (priv->final_position == GTK_POS_TOP)
238 base = allocation->height - TAIL_HEIGHT;
239 tip = allocation->height;
241 else if (priv->final_position == GTK_POS_LEFT)
243 base = allocation->width - TAIL_HEIGHT;
244 tip = allocation->width;
247 if (POS_IS_VERTICAL (priv->final_position))
249 cairo_move_to (cr, CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2,
250 0, allocation->width - TAIL_GAP_WIDTH), base);
251 cairo_line_to (cr, CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2,
252 TAIL_GAP_WIDTH, allocation->width), base);
253 cairo_line_to (cr, CLAMP (x - priv->win_x, 0, allocation->width), tip);
257 cairo_move_to (cr, base,
258 CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2,
259 0, allocation->height - TAIL_GAP_WIDTH));
260 cairo_line_to (cr, base,
261 CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2,
262 TAIL_GAP_WIDTH, allocation->height));
263 cairo_line_to (cr, tip, CLAMP (y - priv->win_y, 0, allocation->height));
266 cairo_close_path (cr);
270 _gtk_bubble_window_apply_border_path (GtkBubbleWindow *window,
273 GtkBubbleWindowPrivate *priv;
274 GtkAllocation allocation;
276 priv = window->_priv;
277 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
279 if (priv->final_position == GTK_POS_TOP)
280 cairo_rectangle (cr, 0, 0, allocation.width, allocation.height - TAIL_HEIGHT);
281 else if (priv->final_position == GTK_POS_BOTTOM)
282 cairo_rectangle (cr, 0, TAIL_HEIGHT , allocation.width,
283 allocation.height - TAIL_HEIGHT);
284 else if (priv->final_position == GTK_POS_LEFT)
285 cairo_rectangle (cr, 0, 0, allocation.width - TAIL_HEIGHT, allocation.height);
286 else if (priv->final_position == GTK_POS_RIGHT)
287 cairo_rectangle (cr, TAIL_HEIGHT, 0,
288 allocation.width - TAIL_HEIGHT, allocation.height);
290 _gtk_bubble_window_apply_tail_path (window, cr, &allocation);
294 _gtk_bubble_window_update_shape (GtkBubbleWindow *window)
296 cairo_surface_t *surface;
297 cairo_region_t *region;
301 win = gtk_widget_get_window (GTK_WIDGET (window));
303 gdk_window_create_similar_surface (win,
304 CAIRO_CONTENT_COLOR_ALPHA,
305 gdk_window_get_width (win),
306 gdk_window_get_height (win));
308 cr = cairo_create (surface);
309 _gtk_bubble_window_apply_border_path (window, cr);
313 region = gdk_cairo_region_create_from_surface (surface);
314 cairo_surface_destroy (surface);
316 if (!gtk_widget_is_composited (GTK_WIDGET (window)))
317 gtk_widget_shape_combine_region (GTK_WIDGET (window), region);
319 gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region);
320 cairo_region_destroy (region);
324 _gtk_bubble_window_update_position (GtkBubbleWindow *window)
326 GtkBubbleWindowPrivate *priv;
327 cairo_rectangle_int_t rect;
328 GtkAllocation allocation;
329 gint win_x, win_y, x, y;
332 priv = window->_priv;
333 screen = gtk_widget_get_screen (GTK_WIDGET (window));
334 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
335 priv->final_position = priv->preferred_position;
336 rect = priv->pointing_to;
338 _gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect);
340 /* Check whether there's enough room on the
341 * preferred side, move to the opposite one if not.
343 if (priv->preferred_position == GTK_POS_TOP && rect.y < allocation.height)
344 priv->final_position = GTK_POS_BOTTOM;
345 else if (priv->preferred_position == GTK_POS_BOTTOM &&
346 rect.y > gdk_screen_get_height (screen) - allocation.height)
347 priv->final_position = GTK_POS_TOP;
348 else if (priv->preferred_position == GTK_POS_LEFT && rect.x < allocation.width)
349 priv->final_position = GTK_POS_RIGHT;
350 else if (priv->preferred_position == GTK_POS_RIGHT &&
351 rect.x > gdk_screen_get_width (screen) - allocation.width)
352 priv->final_position = GTK_POS_LEFT;
354 if (POS_IS_VERTICAL (priv->final_position))
356 win_x = CLAMP (x - allocation.width / 2,
357 0, gdk_screen_get_width (screen) - allocation.width);
360 if (priv->final_position == GTK_POS_TOP)
361 win_y -= allocation.height;
365 win_y = CLAMP (y - allocation.height / 2,
366 0, gdk_screen_get_height (screen) - allocation.height);
369 if (priv->final_position == GTK_POS_LEFT)
370 win_x -= allocation.width;
376 gtk_window_move (GTK_WINDOW (window), win_x, win_y);
378 gtk_widget_queue_resize (GTK_WIDGET (window));
382 gtk_bubble_window_draw (GtkWidget *widget,
385 GtkStyleContext *context;
386 GtkAllocation allocation;
390 context = gtk_widget_get_style_context (widget);
391 gtk_widget_get_allocation (widget, &allocation);
393 if (gtk_widget_is_composited (widget))
396 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
397 cairo_set_source_rgba (cr, 0, 0, 0, 0);
401 _gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
405 gtk_render_background (context, cr, 0, 0,
406 allocation.width, allocation.height);
407 child = gtk_bin_get_child (GTK_BIN (widget));
410 gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
418 gtk_bubble_window_get_preferred_width (GtkWidget *widget,
422 GtkBubbleWindowPrivate *priv;
426 priv = GTK_BUBBLE_WINDOW (widget)->_priv;
427 child = gtk_bin_get_child (GTK_BIN (widget));
431 gtk_widget_get_preferred_width (child, &min, &nat);
433 if (!POS_IS_VERTICAL (priv->final_position))
440 *minimum_width = MAX (min, TAIL_GAP_WIDTH);
443 *natural_width = MAX (nat, TAIL_GAP_WIDTH);
447 gtk_bubble_window_get_preferred_height (GtkWidget *widget,
448 gint *minimum_height,
449 gint *natural_height)
451 GtkBubbleWindowPrivate *priv;
455 priv = GTK_BUBBLE_WINDOW (widget)->_priv;
456 child = gtk_bin_get_child (GTK_BIN (widget));
460 gtk_widget_get_preferred_height (child, &min, &nat);
462 if (POS_IS_VERTICAL (priv->final_position))
469 *minimum_height = MAX (min, TAIL_GAP_WIDTH);
472 *natural_height = MAX (nat, TAIL_GAP_WIDTH);
476 gtk_bubble_window_size_allocate (GtkWidget *widget,
477 GtkAllocation *allocation)
479 GtkBubbleWindowPrivate *priv;
482 priv = GTK_BUBBLE_WINDOW (widget)->_priv;
483 gtk_widget_set_allocation (widget, allocation);
484 child = gtk_bin_get_child (GTK_BIN (widget));
488 GtkAllocation child_alloc;
490 child_alloc.x = child_alloc.y = 0;
491 child_alloc.width = allocation->width;
492 child_alloc.height = allocation->height;
494 if (POS_IS_VERTICAL (priv->final_position))
495 child_alloc.height -= TAIL_HEIGHT;
497 child_alloc.width -= TAIL_HEIGHT;
499 if (priv->final_position == GTK_POS_BOTTOM)
500 child_alloc.y += TAIL_HEIGHT;
501 else if (priv->final_position == GTK_POS_RIGHT)
502 child_alloc.x += TAIL_HEIGHT;
504 gtk_widget_size_allocate (child, &child_alloc);
507 if (gtk_widget_get_realized (widget))
508 _gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget));
510 if (gtk_widget_get_visible (widget))
511 _gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget));
515 gtk_bubble_window_button_press (GtkWidget *widget,
516 GdkEventButton *event)
520 child = gtk_bin_get_child (GTK_BIN (widget));
522 if (child && event->window == gtk_widget_get_window (widget))
524 GtkAllocation child_alloc;
526 gtk_widget_get_allocation (child, &child_alloc);
528 if (event->x < child_alloc.x ||
529 event->x > child_alloc.x + child_alloc.width ||
530 event->y < child_alloc.y ||
531 event->y > child_alloc.y + child_alloc.height)
532 gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
535 gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
537 return GDK_EVENT_PROPAGATE;
541 gtk_bubble_window_key_press (GtkWidget *widget,
544 if (event->keyval == GDK_KEY_Escape)
546 gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
547 return GDK_EVENT_STOP;
550 return GDK_EVENT_PROPAGATE;
554 gtk_bubble_window_grab_broken (GtkWidget *widget,
555 GdkEventGrabBroken *grab_broken)
557 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
558 GtkBubbleWindowPrivate *priv;
559 GdkDevice *event_device;
561 priv = window->_priv;
562 event_device = gdk_event_get_device ((GdkEvent *) grab_broken);
564 if (event_device == priv->device ||
565 event_device == gdk_device_get_associated_device (priv->device))
566 gtk_bubble_window_ungrab (window);
572 gtk_bubble_window_grab_notify (GtkWidget *widget,
573 gboolean was_grabbed)
575 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
576 GtkBubbleWindowPrivate *priv;
578 priv = window->_priv;
580 if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device))
581 gtk_bubble_window_ungrab (window);
585 gtk_bubble_window_screen_changed (GtkWidget *widget,
586 GdkScreen *previous_screen)
591 screen = gtk_widget_get_screen (widget);
592 visual = gdk_screen_get_rgba_visual (screen);
595 gtk_widget_set_visual (widget, visual);
599 gtk_bubble_window_class_init (GtkBubbleWindowClass *klass)
601 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
602 GObjectClass *object_class = G_OBJECT_CLASS (klass);
604 object_class->constructor = gtk_bubble_window_constructor;
605 object_class->set_property = gtk_bubble_window_set_property;
606 object_class->get_property = gtk_bubble_window_get_property;
607 object_class->finalize = gtk_bubble_window_finalize;
609 widget_class->get_preferred_width = gtk_bubble_window_get_preferred_width;
610 widget_class->get_preferred_height = gtk_bubble_window_get_preferred_height;
611 widget_class->size_allocate = gtk_bubble_window_size_allocate;
612 widget_class->draw = gtk_bubble_window_draw;
613 widget_class->button_press_event = gtk_bubble_window_button_press;
614 widget_class->key_press_event = gtk_bubble_window_key_press;
615 widget_class->grab_broken_event = gtk_bubble_window_grab_broken;
616 widget_class->grab_notify = gtk_bubble_window_grab_notify;
617 widget_class->screen_changed = gtk_bubble_window_screen_changed;
619 g_object_class_install_property (object_class,
621 g_param_spec_object ("relative-to",
623 P_("Window the bubble window points to"),
625 GTK_PARAM_READWRITE));
626 g_object_class_install_property (object_class,
628 g_param_spec_boxed ("pointing-to",
630 P_("Rectangle the bubble window points to"),
631 CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
632 GTK_PARAM_READWRITE));
633 g_object_class_install_property (object_class,
635 g_param_spec_enum ("position",
637 P_("Position to place the bubble window"),
638 GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
639 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
641 g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate));
645 _gtk_bubble_window_update_relative_to (GtkBubbleWindow *window,
646 GdkWindow *relative_to)
648 GtkBubbleWindowPrivate *priv;
650 priv = window->_priv;
652 if (priv->relative_to == relative_to)
655 if (priv->relative_to)
656 g_object_unref (priv->relative_to);
658 priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL;
659 g_object_notify (G_OBJECT (window), "relative-to");
663 _gtk_bubble_window_update_pointing_to (GtkBubbleWindow *window,
664 cairo_rectangle_int_t *pointing_to)
666 GtkBubbleWindowPrivate *priv;
668 priv = window->_priv;
669 priv->pointing_to = *pointing_to;
670 priv->has_pointing_to = TRUE;
671 g_object_notify (G_OBJECT (window), "pointing-to");
675 _gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window,
676 GtkPositionType position)
678 GtkBubbleWindowPrivate *priv;
680 priv = window->_priv;
681 priv->preferred_position = position;
682 g_object_notify (G_OBJECT (window), "position");
686 * gtk_bubble_window_new:
688 * Returns a newly created #GtkBubblewindow
690 * Returns: a new #GtkBubbleWindow
695 gtk_bubble_window_new (void)
697 return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL);
701 * gtk_bubble_window_set_relative_to:
702 * @window: a #GtkBubbleWindow
703 * @relative_to: a #GdkWindow
705 * Sets the #GdkWindow to act as the origin of coordinates of
706 * @window, or %NULL to use the root window. See
707 * gtk_bubble_window_set_pointing_to()
709 * If @window is currently visible, it will be moved to reflect
715 gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
716 GdkWindow *relative_to)
718 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
719 g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
721 _gtk_bubble_window_update_relative_to (window, relative_to);
723 if (gtk_widget_get_visible (GTK_WIDGET (window)))
724 _gtk_bubble_window_update_position (window);
728 * gtk_bubble_window_get_relative_to:
729 * @window: a #GtkBubbleWindow
731 * Returns the #GdkWindow used as the origin of coordinates.
732 * If @window is currently visible, it will be moved to reflect
735 * Returns: the #GdkWindow @window is placed upon
740 gtk_bubble_window_get_relative_to (GtkBubbleWindow *window)
742 GtkBubbleWindowPrivate *priv;
744 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL);
746 priv = window->_priv;
748 return priv->relative_to;
752 * gtk_bubble_window_set_pointing_to:
753 * @window: a #GtkBubbleWindow
754 * @rect: rectangle to point to
756 * Sets the rectangle that @window will point to, the coordinates
757 * of this rectangle are relative to the #GdkWindow set through
758 * gtk_bubble_window_set_relative_to().
763 gtk_bubble_window_set_pointing_to (GtkBubbleWindow *window,
764 cairo_rectangle_int_t *rect)
766 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
767 g_return_if_fail (rect != NULL);
769 _gtk_bubble_window_update_pointing_to (window, rect);
771 if (gtk_widget_get_visible (GTK_WIDGET (window)))
772 _gtk_bubble_window_update_position (window);
776 * gtk_bubble_window_get_pointing_to:
777 * @window: a #GtkBubbleWindow
778 * @rect: (out): location to store the rectangle
780 * If a rectangle to point to is set, this function will return
781 * %TRUE and fill in @rect with the rectangle @window is currently
784 * Returns: %TRUE if a rectangle is set
789 gtk_bubble_window_get_pointing_to (GtkBubbleWindow *window,
790 cairo_rectangle_int_t *rect)
792 GtkBubbleWindowPrivate *priv;
794 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
796 priv = window->_priv;
799 *rect = priv->pointing_to;
801 return priv->has_pointing_to;
805 * gtk_bubble_window_set_position:
806 * @window: a #GtkBubbleWindow
807 * @position: preferred bubble position
809 * Sets the preferred position for @window to appear.
810 * If @window is currently visible, it will be moved to reflect
814 * This preference will be respected where possible, although
815 * on lack of space (eg. if close to the screen edges), the
816 * #GtkBubbleWindow may choose to appear on the opposite side
822 gtk_bubble_window_set_position (GtkBubbleWindow *window,
823 GtkPositionType position)
825 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
826 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
828 _gtk_bubble_window_update_preferred_position (window, position);
830 if (gtk_widget_get_visible (GTK_WIDGET (window)))
831 _gtk_bubble_window_update_position (window);
835 * gtk_bubble_window_get_position:
836 * @window: a #GtkBubbleWindow
838 * Returns the preferred position to place @window
840 * Returns: The preferred position
845 gtk_bubble_window_get_position (GtkBubbleWindow *window)
847 GtkBubbleWindowPrivate *priv;
849 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP);
851 priv = window->_priv;
853 return priv->preferred_position;
857 * gtk_bubble_window_popup:
858 * @window: a #GtkBubbleWindow
859 * @relative_to: #GdkWindow to position upon
860 * @pointing_to: rectangle to point to, in @relative_to coordinates
861 * @position: preferred position for @window
863 * This function sets atomically all #GtkBubbleWindow position
864 * parameters, and shows/updates @window
869 gtk_bubble_window_popup (GtkBubbleWindow *window,
870 GdkWindow *relative_to,
871 cairo_rectangle_int_t *pointing_to,
872 GtkPositionType position)
874 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
875 g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
876 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
877 g_return_if_fail (pointing_to != NULL);
879 _gtk_bubble_window_update_preferred_position (window, position);
880 _gtk_bubble_window_update_relative_to (window, relative_to);
881 _gtk_bubble_window_update_pointing_to (window, pointing_to);
883 if (!gtk_widget_get_visible (GTK_WIDGET (window)))
884 gtk_widget_show (GTK_WIDGET (window));
886 _gtk_bubble_window_update_position (window);
890 * gtk_bubble_window_popdown:
891 * @window: a #GtkBubbleWindow
893 * Removes the window from the screen
896 * If a grab was previously added through gtk_bubble_window_grab(),
897 * the grab will be removed by this function.
903 gtk_bubble_window_popdown (GtkBubbleWindow *window)
905 GtkBubbleWindowPrivate *priv = window->_priv;
907 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
910 gtk_bubble_window_ungrab (window);
912 if (gtk_widget_get_visible (GTK_WIDGET (window)))
913 gtk_widget_hide (GTK_WIDGET (window));
917 * gtk_bubble_window_grab:
918 * @window: a #GtkBubbleWindow
919 * @device: a master #GdkDevice
920 * @activate_time: timestamp to perform the grab
922 * This function performs GDK and GTK+ grabs on @device and
923 * its paired #GdkDevice. After this call all pointer/keyboard
924 * events will be handled by @window.
926 * Calling this also brings in a #GtkMenu alike behavior, clicking
927 * outside the #GtkBubbleWindow or pressing the Escape key will
928 * popdown the menu by default.
931 * If there was a previous grab, it will be undone before doing
932 * the requested grab.
935 * Returns: %TRUE if the grab was successful
940 gtk_bubble_window_grab (GtkBubbleWindow *window,
942 guint32 activate_time)
944 GtkBubbleWindowPrivate *priv;
945 GdkDevice *other_device;
946 GdkWindow *grab_window;
947 GdkGrabStatus status;
949 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
950 g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
951 g_return_val_if_fail (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER, FALSE);
953 priv = window->_priv;
955 if (!priv->has_pointing_to ||
956 gdk_window_is_destroyed (priv->relative_to))
960 gtk_bubble_window_ungrab (window);
962 gtk_widget_realize (GTK_WIDGET (window));
963 grab_window = gtk_widget_get_window (GTK_WIDGET (window));
964 other_device = gdk_device_get_associated_device (device);
966 status = gdk_device_grab (device, grab_window,
967 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
968 NULL, activate_time);
970 if (status == GDK_GRAB_SUCCESS)
972 status = gdk_device_grab (other_device, grab_window,
973 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
974 NULL, activate_time);
976 /* Ungrab the first device on error */
977 if (status != GDK_GRAB_SUCCESS)
978 gdk_device_ungrab (device, activate_time);
981 if (status == GDK_GRAB_SUCCESS)
983 gtk_device_grab_add (GTK_WIDGET (window), device, TRUE);
984 priv->device = device;
987 return status == GDK_GRAB_SUCCESS;
991 * gtk_bubble_window_ungrab:
992 * @window: a #GtkBubbleWindow
994 * This functions undoes a grab added through gtk_bubble_window_grab()
1000 gtk_bubble_window_ungrab (GtkBubbleWindow *window)
1002 GtkBubbleWindowPrivate *priv;
1004 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1006 priv = window->_priv;
1011 gdk_device_ungrab (priv->device, GDK_CURRENT_TIME);
1012 gdk_device_ungrab (gdk_device_get_associated_device (priv->device),
1014 gtk_device_grab_remove (GTK_WIDGET (window), priv->device);
1015 priv->device = NULL;