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 "gtktypebuiltins.h"
45 #include "gtkprivate.h"
48 #define TAIL_GAP_WIDTH 24
49 #define TAIL_HEIGHT 12
51 #define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
53 #define GRAB_EVENT_MASK \
54 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
55 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | \
56 GDK_POINTER_MOTION_MASK
58 typedef struct _GtkBubbleWindowPrivate GtkBubbleWindowPrivate;
66 struct _GtkBubbleWindowPrivate
69 GdkWindow *relative_to;
70 cairo_rectangle_int_t pointing_to;
73 guint has_pointing_to : 1;
75 guint preferred_position : 2;
76 guint final_position : 2;
79 G_DEFINE_TYPE (GtkBubbleWindow, gtk_bubble_window, GTK_TYPE_WINDOW)
82 gtk_bubble_window_init (GtkBubbleWindow *window)
88 widget = GTK_WIDGET (window);
89 window->_priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
90 GTK_TYPE_BUBBLE_WINDOW,
91 GtkBubbleWindowPrivate);
92 gtk_window_set_default_size (GTK_WINDOW (window),
93 TAIL_GAP_WIDTH, TAIL_GAP_WIDTH);
94 gtk_widget_set_app_paintable (widget, TRUE);
96 screen = gtk_widget_get_screen (widget);
97 visual = gdk_screen_get_rgba_visual (screen);
100 gtk_widget_set_visual (widget, visual);
104 gtk_bubble_window_constructor (GType type,
105 guint n_construct_properties,
106 GObjectConstructParam *construct_properties)
111 G_OBJECT_CLASS (gtk_bubble_window_parent_class)->constructor (type,
112 n_construct_properties,
113 construct_properties);
114 g_object_set (object, "type", GTK_WINDOW_POPUP, NULL);
120 gtk_bubble_window_set_property (GObject *object,
127 case PROP_RELATIVE_TO:
128 gtk_bubble_window_set_relative_to (GTK_BUBBLE_WINDOW (object),
129 g_value_get_object (value));
131 case PROP_POINTING_TO:
132 gtk_bubble_window_set_pointing_to (GTK_BUBBLE_WINDOW (object),
133 g_value_get_boxed (value));
136 gtk_bubble_window_set_position (GTK_BUBBLE_WINDOW (object),
137 g_value_get_enum (value));
140 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
145 gtk_bubble_window_get_property (GObject *object,
150 GtkBubbleWindowPrivate *priv = GTK_BUBBLE_WINDOW (object)->_priv;
154 case PROP_RELATIVE_TO:
155 g_value_set_object (value, priv->relative_to);
157 case PROP_POINTING_TO:
158 g_value_set_boxed (value, &priv->pointing_to);
161 g_value_set_enum (value, priv->preferred_position);
164 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
169 gtk_bubble_window_finalize (GObject *object)
171 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (object);
172 GtkBubbleWindowPrivate *priv = window->_priv;;
174 gtk_bubble_window_popdown (window);
176 if (priv->relative_to)
177 g_object_unref (priv->relative_to);
179 G_OBJECT_CLASS (gtk_bubble_window_parent_class)->finalize (object);
183 _gtk_bubble_window_get_pointed_to_coords (GtkBubbleWindow *window,
186 cairo_rectangle_int_t *root_rect)
188 GtkBubbleWindowPrivate *priv = window->_priv;
189 cairo_rectangle_int_t rect;
192 rect = priv->pointing_to;
193 screen = gtk_widget_get_screen (GTK_WIDGET (window));
195 if (priv->relative_to)
196 gdk_window_get_root_coords (priv->relative_to,
197 rect.x, rect.y, &rect.x, &rect.y);
199 if (POS_IS_VERTICAL (priv->final_position))
201 *x = CLAMP (rect.x + (rect.width / 2),
202 0, gdk_screen_get_width (screen));
205 if (priv->final_position == GTK_POS_BOTTOM)
210 *y = CLAMP (rect.y + (rect.height / 2),
211 0, gdk_screen_get_height (screen));
214 if (priv->final_position == GTK_POS_RIGHT)
223 _gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window,
225 GtkAllocation *allocation)
227 GtkBubbleWindowPrivate *priv = window->_priv;
228 gint base, tip, x, y;
230 _gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL);
232 if (priv->final_position == GTK_POS_BOTTOM ||
233 priv->final_position == GTK_POS_RIGHT)
238 else if (priv->final_position == GTK_POS_TOP)
240 base = allocation->height - TAIL_HEIGHT;
241 tip = allocation->height;
243 else if (priv->final_position == GTK_POS_LEFT)
245 base = allocation->width - TAIL_HEIGHT;
246 tip = allocation->width;
249 if (POS_IS_VERTICAL (priv->final_position))
251 cairo_move_to (cr, CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2,
252 0, allocation->width - TAIL_GAP_WIDTH), base);
253 cairo_line_to (cr, CLAMP (x - priv->win_x, 0, allocation->width), tip);
254 cairo_line_to (cr, CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2,
255 TAIL_GAP_WIDTH, allocation->width), base);
259 cairo_move_to (cr, base,
260 CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2,
261 0, allocation->height - TAIL_GAP_WIDTH));
262 cairo_line_to (cr, tip, CLAMP (y - priv->win_y, 0, allocation->height));
263 cairo_line_to (cr, base,
264 CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2,
265 TAIL_GAP_WIDTH, allocation->height));
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 _gtk_bubble_window_apply_tail_path (window, cr, &allocation);
281 if (priv->final_position == GTK_POS_TOP)
283 cairo_line_to (cr, allocation.width, allocation.height - TAIL_HEIGHT);
284 cairo_line_to (cr, allocation.width, 0);
285 cairo_line_to (cr, 0, 0);
286 cairo_line_to (cr, 0, allocation.height - TAIL_HEIGHT);
288 else if (priv->final_position == GTK_POS_BOTTOM)
290 cairo_line_to (cr, allocation.width, TAIL_HEIGHT);
291 cairo_line_to (cr, allocation.width, allocation.height);
292 cairo_line_to (cr, 0, allocation.height);
293 cairo_line_to (cr, 0, TAIL_HEIGHT);
295 else if (priv->final_position == GTK_POS_LEFT)
297 cairo_line_to (cr, allocation.width - TAIL_HEIGHT, allocation.height);
298 cairo_line_to (cr, 0, allocation.height);
299 cairo_line_to (cr, 0, 0);
300 cairo_line_to (cr, allocation.width - TAIL_HEIGHT, 0);
302 else if (priv->final_position == GTK_POS_RIGHT)
304 cairo_line_to (cr, TAIL_HEIGHT, 0);
305 cairo_line_to (cr, allocation.width, 0);
306 cairo_line_to (cr, allocation.width, allocation.height);
307 cairo_line_to (cr, TAIL_HEIGHT, allocation.height);
310 cairo_close_path (cr);
314 _gtk_bubble_window_update_shape (GtkBubbleWindow *window)
316 cairo_surface_t *surface;
317 cairo_region_t *region;
321 win = gtk_widget_get_window (GTK_WIDGET (window));
323 gdk_window_create_similar_surface (win,
324 CAIRO_CONTENT_COLOR_ALPHA,
325 gdk_window_get_width (win),
326 gdk_window_get_height (win));
328 cr = cairo_create (surface);
329 _gtk_bubble_window_apply_border_path (window, cr);
333 region = gdk_cairo_region_create_from_surface (surface);
334 cairo_surface_destroy (surface);
336 if (!gtk_widget_is_composited (GTK_WIDGET (window)))
337 gtk_widget_shape_combine_region (GTK_WIDGET (window), region);
339 gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region);
340 cairo_region_destroy (region);
344 _gtk_bubble_window_update_position (GtkBubbleWindow *window)
346 GtkBubbleWindowPrivate *priv;
347 cairo_rectangle_int_t rect;
348 GtkAllocation allocation;
349 gint win_x, win_y, x, y;
352 priv = window->_priv;
353 screen = gtk_widget_get_screen (GTK_WIDGET (window));
354 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
355 priv->final_position = priv->preferred_position;
356 rect = priv->pointing_to;
358 _gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect);
360 /* Check whether there's enough room on the
361 * preferred side, move to the opposite one if not.
363 if (priv->preferred_position == GTK_POS_TOP && rect.y < allocation.height)
364 priv->final_position = GTK_POS_BOTTOM;
365 else if (priv->preferred_position == GTK_POS_BOTTOM &&
366 rect.y > gdk_screen_get_height (screen) - allocation.height)
367 priv->final_position = GTK_POS_TOP;
368 else if (priv->preferred_position == GTK_POS_LEFT && rect.x < allocation.width)
369 priv->final_position = GTK_POS_RIGHT;
370 else if (priv->preferred_position == GTK_POS_RIGHT &&
371 rect.x > gdk_screen_get_width (screen) - allocation.width)
372 priv->final_position = GTK_POS_LEFT;
374 if (POS_IS_VERTICAL (priv->final_position))
376 win_x = CLAMP (x - allocation.width / 2,
377 0, gdk_screen_get_width (screen) - allocation.width);
380 if (priv->final_position == GTK_POS_TOP)
381 win_y -= allocation.height;
385 win_y = CLAMP (y - allocation.height / 2,
386 0, gdk_screen_get_height (screen) - allocation.height);
389 if (priv->final_position == GTK_POS_LEFT)
390 win_x -= allocation.width;
396 gtk_window_move (GTK_WINDOW (window), win_x, win_y);
398 gtk_widget_queue_resize (GTK_WIDGET (window));
402 gtk_bubble_window_draw (GtkWidget *widget,
405 GtkStyleContext *context;
406 GtkAllocation allocation;
411 context = gtk_widget_get_style_context (widget);
412 gtk_widget_get_allocation (widget, &allocation);
414 gtk_render_background (context, cr, 0, 0,
415 allocation.width, allocation.height);
417 if (gtk_widget_is_composited (widget))
420 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
421 cairo_set_source_rgba (cr, 0, 0, 0, 0);
425 _gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
429 gtk_render_background (context, cr, 0, 0,
430 allocation.width, allocation.height);
432 gtk_style_context_get (context, gtk_widget_get_state_flags (widget),
433 GTK_STYLE_PROPERTY_BORDER_COLOR, &border,
436 _gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
437 gdk_cairo_set_source_rgba (cr, border);
440 child = gtk_bin_get_child (GTK_BIN (widget));
443 gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
447 gdk_rgba_free (border);
453 gtk_bubble_window_get_preferred_width (GtkWidget *widget,
457 GtkBubbleWindowPrivate *priv;
461 priv = GTK_BUBBLE_WINDOW (widget)->_priv;
462 child = gtk_bin_get_child (GTK_BIN (widget));
466 gtk_widget_get_preferred_width (child, &min, &nat);
468 if (!POS_IS_VERTICAL (priv->final_position))
475 *minimum_width = MAX (min, TAIL_GAP_WIDTH);
478 *natural_width = MAX (nat, TAIL_GAP_WIDTH);
482 gtk_bubble_window_get_preferred_height (GtkWidget *widget,
483 gint *minimum_height,
484 gint *natural_height)
486 GtkBubbleWindowPrivate *priv;
490 priv = GTK_BUBBLE_WINDOW (widget)->_priv;
491 child = gtk_bin_get_child (GTK_BIN (widget));
495 gtk_widget_get_preferred_height (child, &min, &nat);
497 if (POS_IS_VERTICAL (priv->final_position))
504 *minimum_height = MAX (min, TAIL_GAP_WIDTH);
507 *natural_height = MAX (nat, TAIL_GAP_WIDTH);
511 gtk_bubble_window_size_allocate (GtkWidget *widget,
512 GtkAllocation *allocation)
514 GtkBubbleWindowPrivate *priv;
517 priv = GTK_BUBBLE_WINDOW (widget)->_priv;
518 gtk_widget_set_allocation (widget, allocation);
519 child = gtk_bin_get_child (GTK_BIN (widget));
523 GtkAllocation child_alloc;
525 child_alloc.x = child_alloc.y = 0;
526 child_alloc.width = allocation->width;
527 child_alloc.height = allocation->height;
529 if (POS_IS_VERTICAL (priv->final_position))
530 child_alloc.height -= TAIL_HEIGHT;
532 child_alloc.width -= TAIL_HEIGHT;
534 if (priv->final_position == GTK_POS_BOTTOM)
535 child_alloc.y += TAIL_HEIGHT;
536 else if (priv->final_position == GTK_POS_RIGHT)
537 child_alloc.x += TAIL_HEIGHT;
539 gtk_widget_size_allocate (child, &child_alloc);
542 if (gtk_widget_get_realized (widget))
543 _gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget));
545 if (gtk_widget_get_visible (widget))
546 _gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget));
550 gtk_bubble_window_button_press (GtkWidget *widget,
551 GdkEventButton *event)
555 child = gtk_bin_get_child (GTK_BIN (widget));
557 if (child && event->window == gtk_widget_get_window (widget))
559 GtkAllocation child_alloc;
561 gtk_widget_get_allocation (child, &child_alloc);
563 if (event->x < child_alloc.x ||
564 event->x > child_alloc.x + child_alloc.width ||
565 event->y < child_alloc.y ||
566 event->y > child_alloc.y + child_alloc.height)
567 gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
570 gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
572 return GDK_EVENT_PROPAGATE;
576 gtk_bubble_window_key_press (GtkWidget *widget,
579 if (event->keyval == GDK_KEY_Escape)
581 gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
582 return GDK_EVENT_STOP;
585 return GDK_EVENT_PROPAGATE;
589 gtk_bubble_window_grab_broken (GtkWidget *widget,
590 GdkEventGrabBroken *grab_broken)
592 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
593 GtkBubbleWindowPrivate *priv;
594 GdkDevice *event_device;
596 priv = window->_priv;
597 event_device = gdk_event_get_device ((GdkEvent *) grab_broken);
599 if (event_device == priv->device ||
600 event_device == gdk_device_get_associated_device (priv->device))
601 gtk_bubble_window_ungrab (window);
607 gtk_bubble_window_grab_notify (GtkWidget *widget,
608 gboolean was_grabbed)
610 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
611 GtkBubbleWindowPrivate *priv;
613 priv = window->_priv;
615 if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device))
616 gtk_bubble_window_ungrab (window);
620 gtk_bubble_window_screen_changed (GtkWidget *widget,
621 GdkScreen *previous_screen)
626 screen = gtk_widget_get_screen (widget);
627 visual = gdk_screen_get_rgba_visual (screen);
630 gtk_widget_set_visual (widget, visual);
634 gtk_bubble_window_class_init (GtkBubbleWindowClass *klass)
636 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
637 GObjectClass *object_class = G_OBJECT_CLASS (klass);
639 object_class->constructor = gtk_bubble_window_constructor;
640 object_class->set_property = gtk_bubble_window_set_property;
641 object_class->get_property = gtk_bubble_window_get_property;
642 object_class->finalize = gtk_bubble_window_finalize;
644 widget_class->get_preferred_width = gtk_bubble_window_get_preferred_width;
645 widget_class->get_preferred_height = gtk_bubble_window_get_preferred_height;
646 widget_class->size_allocate = gtk_bubble_window_size_allocate;
647 widget_class->draw = gtk_bubble_window_draw;
648 widget_class->button_press_event = gtk_bubble_window_button_press;
649 widget_class->key_press_event = gtk_bubble_window_key_press;
650 widget_class->grab_broken_event = gtk_bubble_window_grab_broken;
651 widget_class->grab_notify = gtk_bubble_window_grab_notify;
652 widget_class->screen_changed = gtk_bubble_window_screen_changed;
654 g_object_class_install_property (object_class,
656 g_param_spec_object ("relative-to",
658 P_("Window the bubble window points to"),
660 GTK_PARAM_READWRITE));
661 g_object_class_install_property (object_class,
663 g_param_spec_boxed ("pointing-to",
665 P_("Rectangle the bubble window points to"),
666 CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
667 GTK_PARAM_READWRITE));
668 g_object_class_install_property (object_class,
670 g_param_spec_enum ("position",
672 P_("Position to place the bubble window"),
673 GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
674 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
676 g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate));
680 _gtk_bubble_window_update_relative_to (GtkBubbleWindow *window,
681 GdkWindow *relative_to)
683 GtkBubbleWindowPrivate *priv;
685 priv = window->_priv;
687 if (priv->relative_to == relative_to)
690 if (priv->relative_to)
691 g_object_unref (priv->relative_to);
693 priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL;
694 g_object_notify (G_OBJECT (window), "relative-to");
698 _gtk_bubble_window_update_pointing_to (GtkBubbleWindow *window,
699 cairo_rectangle_int_t *pointing_to)
701 GtkBubbleWindowPrivate *priv;
703 priv = window->_priv;
704 priv->pointing_to = *pointing_to;
705 priv->has_pointing_to = TRUE;
706 g_object_notify (G_OBJECT (window), "pointing-to");
710 _gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window,
711 GtkPositionType position)
713 GtkBubbleWindowPrivate *priv;
715 priv = window->_priv;
716 priv->preferred_position = position;
717 g_object_notify (G_OBJECT (window), "position");
721 * gtk_bubble_window_new:
723 * Returns a newly created #GtkBubblewindow
725 * Returns: a new #GtkBubbleWindow
730 gtk_bubble_window_new (void)
732 return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL);
736 * gtk_bubble_window_set_relative_to:
737 * @window: a #GtkBubbleWindow
738 * @relative_to: a #GdkWindow
740 * Sets the #GdkWindow to act as the origin of coordinates of
741 * @window, or %NULL to use the root window. See
742 * gtk_bubble_window_set_pointing_to()
744 * If @window is currently visible, it will be moved to reflect
750 gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
751 GdkWindow *relative_to)
753 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
754 g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
756 _gtk_bubble_window_update_relative_to (window, relative_to);
758 if (gtk_widget_get_visible (GTK_WIDGET (window)))
759 _gtk_bubble_window_update_position (window);
763 * gtk_bubble_window_get_relative_to:
764 * @window: a #GtkBubbleWindow
766 * Returns the #GdkWindow used as the origin of coordinates.
767 * If @window is currently visible, it will be moved to reflect
770 * Returns: the #GdkWindow @window is placed upon
775 gtk_bubble_window_get_relative_to (GtkBubbleWindow *window)
777 GtkBubbleWindowPrivate *priv;
779 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL);
781 priv = window->_priv;
783 return priv->relative_to;
787 * gtk_bubble_window_set_pointing_to:
788 * @window: a #GtkBubbleWindow
789 * @rect: rectangle to point to
791 * Sets the rectangle that @window will point to, the coordinates
792 * of this rectangle are relative to the #GdkWindow set through
793 * gtk_bubble_window_set_relative_to().
798 gtk_bubble_window_set_pointing_to (GtkBubbleWindow *window,
799 cairo_rectangle_int_t *rect)
801 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
802 g_return_if_fail (rect != NULL);
804 _gtk_bubble_window_update_pointing_to (window, rect);
806 if (gtk_widget_get_visible (GTK_WIDGET (window)))
807 _gtk_bubble_window_update_position (window);
811 * gtk_bubble_window_get_pointing_to:
812 * @window: a #GtkBubbleWindow
813 * @rect: (out): location to store the rectangle
815 * If a rectangle to point to is set, this function will return
816 * %TRUE and fill in @rect with the rectangle @window is currently
819 * Returns: %TRUE if a rectangle is set
824 gtk_bubble_window_get_pointing_to (GtkBubbleWindow *window,
825 cairo_rectangle_int_t *rect)
827 GtkBubbleWindowPrivate *priv;
829 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
831 priv = window->_priv;
834 *rect = priv->pointing_to;
836 return priv->has_pointing_to;
840 * gtk_bubble_window_set_position:
841 * @window: a #GtkBubbleWindow
842 * @position: preferred bubble position
844 * Sets the preferred position for @window to appear.
845 * If @window is currently visible, it will be moved to reflect
849 * This preference will be respected where possible, although
850 * on lack of space (eg. if close to the screen edges), the
851 * #GtkBubbleWindow may choose to appear on the opposite side
857 gtk_bubble_window_set_position (GtkBubbleWindow *window,
858 GtkPositionType position)
860 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
861 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
863 _gtk_bubble_window_update_preferred_position (window, position);
865 if (gtk_widget_get_visible (GTK_WIDGET (window)))
866 _gtk_bubble_window_update_position (window);
870 * gtk_bubble_window_get_position:
871 * @window: a #GtkBubbleWindow
873 * Returns the preferred position to place @window
875 * Returns: The preferred position
880 gtk_bubble_window_get_position (GtkBubbleWindow *window)
882 GtkBubbleWindowPrivate *priv;
884 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP);
886 priv = window->_priv;
888 return priv->preferred_position;
892 * gtk_bubble_window_popup:
893 * @window: a #GtkBubbleWindow
894 * @relative_to: #GdkWindow to position upon
895 * @pointing_to: rectangle to point to, in @relative_to coordinates
896 * @position: preferred position for @window
898 * This function sets atomically all #GtkBubbleWindow position
899 * parameters, and shows/updates @window
904 gtk_bubble_window_popup (GtkBubbleWindow *window,
905 GdkWindow *relative_to,
906 cairo_rectangle_int_t *pointing_to,
907 GtkPositionType position)
909 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
910 g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
911 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
912 g_return_if_fail (pointing_to != NULL);
914 _gtk_bubble_window_update_preferred_position (window, position);
915 _gtk_bubble_window_update_relative_to (window, relative_to);
916 _gtk_bubble_window_update_pointing_to (window, pointing_to);
918 if (!gtk_widget_get_visible (GTK_WIDGET (window)))
919 gtk_widget_show (GTK_WIDGET (window));
921 _gtk_bubble_window_update_position (window);
925 * gtk_bubble_window_popdown:
926 * @window: a #GtkBubbleWindow
928 * Removes the window from the screen
931 * If a grab was previously added through gtk_bubble_window_grab(),
932 * the grab will be removed by this function.
938 gtk_bubble_window_popdown (GtkBubbleWindow *window)
940 GtkBubbleWindowPrivate *priv = window->_priv;
942 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
945 gtk_bubble_window_ungrab (window);
947 if (gtk_widget_get_visible (GTK_WIDGET (window)))
948 gtk_widget_hide (GTK_WIDGET (window));
952 * gtk_bubble_window_grab:
953 * @window: a #GtkBubbleWindow
954 * @device: a master #GdkDevice
955 * @activate_time: timestamp to perform the grab
957 * This function performs GDK and GTK+ grabs on @device and
958 * its paired #GdkDevice. After this call all pointer/keyboard
959 * events will be handled by @window.
961 * Calling this also brings in a #GtkMenu alike behavior, clicking
962 * outside the #GtkBubbleWindow or pressing the Escape key will
963 * popdown the menu by default.
966 * If there was a previous grab, it will be undone before doing
967 * the requested grab.
970 * Returns: %TRUE if the grab was successful
975 gtk_bubble_window_grab (GtkBubbleWindow *window,
977 guint32 activate_time)
979 GtkBubbleWindowPrivate *priv;
980 GdkDevice *other_device;
981 GdkWindow *grab_window;
982 GdkGrabStatus status;
984 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
985 g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
986 g_return_val_if_fail (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER, FALSE);
988 priv = window->_priv;
990 if (!priv->has_pointing_to ||
991 gdk_window_is_destroyed (priv->relative_to))
995 gtk_bubble_window_ungrab (window);
997 gtk_widget_realize (GTK_WIDGET (window));
998 grab_window = gtk_widget_get_window (GTK_WIDGET (window));
999 other_device = gdk_device_get_associated_device (device);
1001 status = gdk_device_grab (device, grab_window,
1002 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1003 NULL, activate_time);
1005 if (status == GDK_GRAB_SUCCESS)
1007 status = gdk_device_grab (other_device, grab_window,
1008 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1009 NULL, activate_time);
1011 /* Ungrab the first device on error */
1012 if (status != GDK_GRAB_SUCCESS)
1013 gdk_device_ungrab (device, activate_time);
1016 if (status == GDK_GRAB_SUCCESS)
1018 gtk_device_grab_add (GTK_WIDGET (window), device, TRUE);
1019 priv->device = device;
1022 return status == GDK_GRAB_SUCCESS;
1026 * gtk_bubble_window_ungrab:
1027 * @window: a #GtkBubbleWindow
1029 * This functions undoes a grab added through gtk_bubble_window_grab()
1035 gtk_bubble_window_ungrab (GtkBubbleWindow *window)
1037 GtkBubbleWindowPrivate *priv;
1039 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1041 priv = window->_priv;
1046 gdk_device_ungrab (priv->device, GDK_CURRENT_TIME);
1047 gdk_device_ungrab (gdk_device_get_associated_device (priv->device),
1049 gtk_device_grab_remove (GTK_WIDGET (window), priv->device);
1050 priv->device = NULL;