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 * GtkBubbleWindow is a bubble-like context window, primarily mean for
20 * context-dependent helpers on touch interfaces.
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
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().
37 #include <cairo-gobject.h>
38 #include "gtkbubblewindowprivate.h"
39 #include "gtktypebuiltins.h"
41 #include "gtkprivate.h"
44 #define TAIL_GAP_WIDTH 24
45 #define TAIL_HEIGHT 12
47 #define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
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
54 typedef struct _GtkBubbleWindowPrivate GtkBubbleWindowPrivate;
62 struct _GtkBubbleWindowPrivate
65 GdkWindow *relative_to;
66 cairo_rectangle_int_t pointing_to;
69 guint has_pointing_to : 1;
71 guint preferred_position : 2;
72 guint final_position : 2;
75 G_DEFINE_TYPE (GtkBubbleWindow, _gtk_bubble_window, GTK_TYPE_WINDOW)
78 _gtk_bubble_window_init (GtkBubbleWindow *window)
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);
92 screen = gtk_widget_get_screen (widget);
93 visual = gdk_screen_get_rgba_visual (screen);
96 gtk_widget_set_visual (widget, visual);
100 gtk_bubble_window_constructor (GType type,
101 guint n_construct_properties,
102 GObjectConstructParam *construct_properties)
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);
116 gtk_bubble_window_set_property (GObject *object,
123 case PROP_RELATIVE_TO:
124 _gtk_bubble_window_set_relative_to (GTK_BUBBLE_WINDOW (object),
125 g_value_get_object (value));
127 case PROP_POINTING_TO:
128 _gtk_bubble_window_set_pointing_to (GTK_BUBBLE_WINDOW (object),
129 g_value_get_boxed (value));
132 _gtk_bubble_window_set_position (GTK_BUBBLE_WINDOW (object),
133 g_value_get_enum (value));
136 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
141 gtk_bubble_window_get_property (GObject *object,
146 GtkBubbleWindowPrivate *priv = GTK_BUBBLE_WINDOW (object)->priv;
150 case PROP_RELATIVE_TO:
151 g_value_set_object (value, priv->relative_to);
153 case PROP_POINTING_TO:
154 g_value_set_boxed (value, &priv->pointing_to);
157 g_value_set_enum (value, priv->preferred_position);
160 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
165 gtk_bubble_window_finalize (GObject *object)
167 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (object);
168 GtkBubbleWindowPrivate *priv = window->priv;
170 _gtk_bubble_window_popdown (window);
172 if (priv->relative_to)
173 g_object_unref (priv->relative_to);
175 G_OBJECT_CLASS (_gtk_bubble_window_parent_class)->finalize (object);
179 gtk_bubble_window_get_pointed_to_coords (GtkBubbleWindow *window,
182 cairo_rectangle_int_t *root_rect)
184 GtkBubbleWindowPrivate *priv = window->priv;
185 cairo_rectangle_int_t rect;
188 rect = priv->pointing_to;
189 screen = gtk_widget_get_screen (GTK_WIDGET (window));
191 if (priv->relative_to)
192 gdk_window_get_root_coords (priv->relative_to,
193 rect.x, rect.y, &rect.x, &rect.y);
195 if (POS_IS_VERTICAL (priv->final_position))
197 *x = CLAMP (rect.x + (rect.width / 2),
198 0, gdk_screen_get_width (screen));
201 if (priv->final_position == GTK_POS_BOTTOM)
206 *y = CLAMP (rect.y + (rect.height / 2),
207 0, gdk_screen_get_height (screen));
210 if (priv->final_position == GTK_POS_RIGHT)
219 gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window,
221 GtkAllocation *allocation)
223 GtkBubbleWindowPrivate *priv = window->priv;
224 gint base, tip, x, y;
226 gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL);
228 if (priv->final_position == GTK_POS_BOTTOM ||
229 priv->final_position == GTK_POS_RIGHT)
234 else if (priv->final_position == GTK_POS_TOP)
236 base = allocation->height - TAIL_HEIGHT;
237 tip = allocation->height;
239 else if (priv->final_position == GTK_POS_LEFT)
241 base = allocation->width - TAIL_HEIGHT;
242 tip = allocation->width;
245 if (POS_IS_VERTICAL (priv->final_position))
247 cairo_move_to (cr, CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2,
248 0, allocation->width - TAIL_GAP_WIDTH), base);
249 cairo_line_to (cr, CLAMP (x - priv->win_x, 0, allocation->width), tip);
250 cairo_line_to (cr, CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2,
251 TAIL_GAP_WIDTH, allocation->width), base);
255 cairo_move_to (cr, base,
256 CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2,
257 0, allocation->height - TAIL_GAP_WIDTH));
258 cairo_line_to (cr, tip, CLAMP (y - priv->win_y, 0, allocation->height));
259 cairo_line_to (cr, base,
260 CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2,
261 TAIL_GAP_WIDTH, allocation->height));
266 gtk_bubble_window_apply_border_path (GtkBubbleWindow *window,
269 GtkBubbleWindowPrivate *priv;
270 GtkAllocation allocation;
273 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
275 gtk_bubble_window_apply_tail_path (window, cr, &allocation);
277 if (priv->final_position == GTK_POS_TOP)
279 cairo_line_to (cr, allocation.width, allocation.height - TAIL_HEIGHT);
280 cairo_line_to (cr, allocation.width, 0);
281 cairo_line_to (cr, 0, 0);
282 cairo_line_to (cr, 0, allocation.height - TAIL_HEIGHT);
284 else if (priv->final_position == GTK_POS_BOTTOM)
286 cairo_line_to (cr, allocation.width, TAIL_HEIGHT);
287 cairo_line_to (cr, allocation.width, allocation.height);
288 cairo_line_to (cr, 0, allocation.height);
289 cairo_line_to (cr, 0, TAIL_HEIGHT);
291 else if (priv->final_position == GTK_POS_LEFT)
293 cairo_line_to (cr, allocation.width - TAIL_HEIGHT, allocation.height);
294 cairo_line_to (cr, 0, allocation.height);
295 cairo_line_to (cr, 0, 0);
296 cairo_line_to (cr, allocation.width - TAIL_HEIGHT, 0);
298 else if (priv->final_position == GTK_POS_RIGHT)
300 cairo_line_to (cr, TAIL_HEIGHT, 0);
301 cairo_line_to (cr, allocation.width, 0);
302 cairo_line_to (cr, allocation.width, allocation.height);
303 cairo_line_to (cr, TAIL_HEIGHT, allocation.height);
306 cairo_close_path (cr);
310 gtk_bubble_window_update_shape (GtkBubbleWindow *window)
312 cairo_surface_t *surface;
313 cairo_region_t *region;
317 win = gtk_widget_get_window (GTK_WIDGET (window));
319 gdk_window_create_similar_surface (win,
320 CAIRO_CONTENT_COLOR_ALPHA,
321 gdk_window_get_width (win),
322 gdk_window_get_height (win));
324 cr = cairo_create (surface);
325 gtk_bubble_window_apply_border_path (window, cr);
329 region = gdk_cairo_region_create_from_surface (surface);
330 cairo_surface_destroy (surface);
332 if (!gtk_widget_is_composited (GTK_WIDGET (window)))
333 gtk_widget_shape_combine_region (GTK_WIDGET (window), region);
335 gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region);
336 cairo_region_destroy (region);
340 gtk_bubble_window_update_position (GtkBubbleWindow *window)
342 GtkBubbleWindowPrivate *priv;
343 cairo_rectangle_int_t rect;
344 GtkAllocation allocation;
345 gint win_x, win_y, x, y;
349 screen = gtk_widget_get_screen (GTK_WIDGET (window));
350 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
351 priv->final_position = priv->preferred_position;
352 rect = priv->pointing_to;
354 gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect);
356 /* Check whether there's enough room on the
357 * preferred side, move to the opposite one if not.
359 if (priv->preferred_position == GTK_POS_TOP && rect.y < allocation.height)
360 priv->final_position = GTK_POS_BOTTOM;
361 else if (priv->preferred_position == GTK_POS_BOTTOM &&
362 rect.y > gdk_screen_get_height (screen) - allocation.height)
363 priv->final_position = GTK_POS_TOP;
364 else if (priv->preferred_position == GTK_POS_LEFT && rect.x < allocation.width)
365 priv->final_position = GTK_POS_RIGHT;
366 else if (priv->preferred_position == GTK_POS_RIGHT &&
367 rect.x > gdk_screen_get_width (screen) - allocation.width)
368 priv->final_position = GTK_POS_LEFT;
370 if (POS_IS_VERTICAL (priv->final_position))
372 win_x = CLAMP (x - allocation.width / 2,
373 0, gdk_screen_get_width (screen) - allocation.width);
376 if (priv->final_position == GTK_POS_TOP)
377 win_y -= allocation.height;
381 win_y = CLAMP (y - allocation.height / 2,
382 0, gdk_screen_get_height (screen) - allocation.height);
385 if (priv->final_position == GTK_POS_LEFT)
386 win_x -= allocation.width;
392 gtk_window_move (GTK_WINDOW (window), win_x, win_y);
394 gtk_widget_queue_resize (GTK_WIDGET (window));
398 gtk_bubble_window_draw (GtkWidget *widget,
401 GtkStyleContext *context;
402 GtkAllocation allocation;
407 context = gtk_widget_get_style_context (widget);
408 gtk_widget_get_allocation (widget, &allocation);
410 gtk_render_background (context, cr, 0, 0,
411 allocation.width, allocation.height);
413 if (gtk_widget_is_composited (widget))
416 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
417 cairo_set_source_rgba (cr, 0, 0, 0, 0);
421 gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
425 gtk_render_background (context, cr, 0, 0,
426 allocation.width, allocation.height);
428 gtk_style_context_get (context, gtk_widget_get_state_flags (widget),
429 GTK_STYLE_PROPERTY_BORDER_COLOR, &border,
432 gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
433 gdk_cairo_set_source_rgba (cr, border);
436 child = gtk_bin_get_child (GTK_BIN (widget));
439 gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
443 gdk_rgba_free (border);
449 gtk_bubble_window_get_preferred_width (GtkWidget *widget,
453 GtkBubbleWindowPrivate *priv;
457 priv = GTK_BUBBLE_WINDOW (widget)->priv;
458 child = gtk_bin_get_child (GTK_BIN (widget));
462 gtk_widget_get_preferred_width (child, &min, &nat);
464 if (!POS_IS_VERTICAL (priv->final_position))
471 *minimum_width = MAX (min, TAIL_GAP_WIDTH);
474 *natural_width = MAX (nat, TAIL_GAP_WIDTH);
478 gtk_bubble_window_get_preferred_height (GtkWidget *widget,
479 gint *minimum_height,
480 gint *natural_height)
482 GtkBubbleWindowPrivate *priv;
486 priv = GTK_BUBBLE_WINDOW (widget)->priv;
487 child = gtk_bin_get_child (GTK_BIN (widget));
491 gtk_widget_get_preferred_height (child, &min, &nat);
493 if (POS_IS_VERTICAL (priv->final_position))
500 *minimum_height = MAX (min, TAIL_GAP_WIDTH);
503 *natural_height = MAX (nat, TAIL_GAP_WIDTH);
507 gtk_bubble_window_size_allocate (GtkWidget *widget,
508 GtkAllocation *allocation)
510 GtkBubbleWindowPrivate *priv;
513 priv = GTK_BUBBLE_WINDOW (widget)->priv;
514 gtk_widget_set_allocation (widget, allocation);
515 child = gtk_bin_get_child (GTK_BIN (widget));
519 GtkAllocation child_alloc;
521 child_alloc.x = child_alloc.y = 0;
522 child_alloc.width = allocation->width;
523 child_alloc.height = allocation->height;
525 if (POS_IS_VERTICAL (priv->final_position))
526 child_alloc.height -= TAIL_HEIGHT;
528 child_alloc.width -= TAIL_HEIGHT;
530 if (priv->final_position == GTK_POS_BOTTOM)
531 child_alloc.y += TAIL_HEIGHT;
532 else if (priv->final_position == GTK_POS_RIGHT)
533 child_alloc.x += TAIL_HEIGHT;
535 gtk_widget_size_allocate (child, &child_alloc);
538 if (gtk_widget_get_realized (widget))
539 gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget));
541 if (gtk_widget_get_visible (widget))
542 gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget));
546 gtk_bubble_window_button_press (GtkWidget *widget,
547 GdkEventButton *event)
551 child = gtk_bin_get_child (GTK_BIN (widget));
553 if (child && event->window == gtk_widget_get_window (widget))
555 GtkAllocation child_alloc;
557 gtk_widget_get_allocation (child, &child_alloc);
559 if (event->x < child_alloc.x ||
560 event->x > child_alloc.x + child_alloc.width ||
561 event->y < child_alloc.y ||
562 event->y > child_alloc.y + child_alloc.height)
563 _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
566 _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
568 return GDK_EVENT_PROPAGATE;
572 gtk_bubble_window_key_press (GtkWidget *widget,
575 if (event->keyval == GDK_KEY_Escape)
577 _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
578 return GDK_EVENT_STOP;
581 return GDK_EVENT_PROPAGATE;
585 gtk_bubble_window_grab_broken (GtkWidget *widget,
586 GdkEventGrabBroken *grab_broken)
588 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
589 GtkBubbleWindowPrivate *priv;
590 GdkDevice *event_device;
593 event_device = gdk_event_get_device ((GdkEvent *) grab_broken);
595 if (event_device == priv->device ||
596 event_device == gdk_device_get_associated_device (priv->device))
597 _gtk_bubble_window_ungrab (window);
603 gtk_bubble_window_grab_notify (GtkWidget *widget,
604 gboolean was_grabbed)
606 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
607 GtkBubbleWindowPrivate *priv;
611 if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device))
612 _gtk_bubble_window_ungrab (window);
616 gtk_bubble_window_screen_changed (GtkWidget *widget,
617 GdkScreen *previous_screen)
622 screen = gtk_widget_get_screen (widget);
623 visual = gdk_screen_get_rgba_visual (screen);
626 gtk_widget_set_visual (widget, visual);
630 _gtk_bubble_window_class_init (GtkBubbleWindowClass *klass)
632 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
633 GObjectClass *object_class = G_OBJECT_CLASS (klass);
635 object_class->constructor = gtk_bubble_window_constructor;
636 object_class->set_property = gtk_bubble_window_set_property;
637 object_class->get_property = gtk_bubble_window_get_property;
638 object_class->finalize = gtk_bubble_window_finalize;
640 widget_class->get_preferred_width = gtk_bubble_window_get_preferred_width;
641 widget_class->get_preferred_height = gtk_bubble_window_get_preferred_height;
642 widget_class->size_allocate = gtk_bubble_window_size_allocate;
643 widget_class->draw = gtk_bubble_window_draw;
644 widget_class->button_press_event = gtk_bubble_window_button_press;
645 widget_class->key_press_event = gtk_bubble_window_key_press;
646 widget_class->grab_broken_event = gtk_bubble_window_grab_broken;
647 widget_class->grab_notify = gtk_bubble_window_grab_notify;
648 widget_class->screen_changed = gtk_bubble_window_screen_changed;
650 g_object_class_install_property (object_class,
652 g_param_spec_object ("relative-to",
654 P_("Window the bubble window points to"),
656 GTK_PARAM_READWRITE));
657 g_object_class_install_property (object_class,
659 g_param_spec_boxed ("pointing-to",
661 P_("Rectangle the bubble window points to"),
662 CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
663 GTK_PARAM_READWRITE));
664 g_object_class_install_property (object_class,
666 g_param_spec_enum ("position",
668 P_("Position to place the bubble window"),
669 GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
670 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
672 g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate));
676 gtk_bubble_window_update_relative_to (GtkBubbleWindow *window,
677 GdkWindow *relative_to)
679 GtkBubbleWindowPrivate *priv;
683 if (priv->relative_to == relative_to)
686 if (priv->relative_to)
687 g_object_unref (priv->relative_to);
689 priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL;
690 g_object_notify (G_OBJECT (window), "relative-to");
694 gtk_bubble_window_update_pointing_to (GtkBubbleWindow *window,
695 cairo_rectangle_int_t *pointing_to)
697 GtkBubbleWindowPrivate *priv;
700 priv->pointing_to = *pointing_to;
701 priv->has_pointing_to = TRUE;
702 g_object_notify (G_OBJECT (window), "pointing-to");
706 gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window,
707 GtkPositionType position)
709 GtkBubbleWindowPrivate *priv;
712 priv->preferred_position = position;
713 g_object_notify (G_OBJECT (window), "position");
717 * gtk_bubble_window_new:
719 * Returns a newly created #GtkBubblewindow
721 * Returns: a new #GtkBubbleWindow
726 _gtk_bubble_window_new (void)
728 return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL);
732 * gtk_bubble_window_set_relative_to:
733 * @window: a #GtkBubbleWindow
734 * @relative_to: a #GdkWindow
736 * Sets the #GdkWindow to act as the origin of coordinates of
737 * @window, or %NULL to use the root window. See
738 * gtk_bubble_window_set_pointing_to()
740 * If @window is currently visible, it will be moved to reflect
746 _gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
747 GdkWindow *relative_to)
749 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
750 g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
752 gtk_bubble_window_update_relative_to (window, relative_to);
754 if (gtk_widget_get_visible (GTK_WIDGET (window)))
755 gtk_bubble_window_update_position (window);
759 * gtk_bubble_window_get_relative_to:
760 * @window: a #GtkBubbleWindow
762 * Returns the #GdkWindow used as the origin of coordinates.
763 * If @window is currently visible, it will be moved to reflect
766 * Returns: the #GdkWindow @window is placed upon
771 _gtk_bubble_window_get_relative_to (GtkBubbleWindow *window)
773 GtkBubbleWindowPrivate *priv;
775 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL);
779 return priv->relative_to;
783 * gtk_bubble_window_set_pointing_to:
784 * @window: a #GtkBubbleWindow
785 * @rect: rectangle to point to
787 * Sets the rectangle that @window will point to, the coordinates
788 * of this rectangle are relative to the #GdkWindow set through
789 * gtk_bubble_window_set_relative_to().
794 _gtk_bubble_window_set_pointing_to (GtkBubbleWindow *window,
795 cairo_rectangle_int_t *rect)
797 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
798 g_return_if_fail (rect != NULL);
800 gtk_bubble_window_update_pointing_to (window, rect);
802 if (gtk_widget_get_visible (GTK_WIDGET (window)))
803 gtk_bubble_window_update_position (window);
807 * gtk_bubble_window_get_pointing_to:
808 * @window: a #GtkBubbleWindow
809 * @rect: (out): location to store the rectangle
811 * If a rectangle to point to is set, this function will return
812 * %TRUE and fill in @rect with the rectangle @window is currently
815 * Returns: %TRUE if a rectangle is set
820 _gtk_bubble_window_get_pointing_to (GtkBubbleWindow *window,
821 cairo_rectangle_int_t *rect)
823 GtkBubbleWindowPrivate *priv;
825 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
830 *rect = priv->pointing_to;
832 return priv->has_pointing_to;
836 * gtk_bubble_window_set_position:
837 * @window: a #GtkBubbleWindow
838 * @position: preferred bubble position
840 * Sets the preferred position for @window to appear.
841 * If @window is currently visible, it will be moved to reflect
845 * This preference will be respected where possible, although
846 * on lack of space (eg. if close to the screen edges), the
847 * #GtkBubbleWindow may choose to appear on the opposite side
853 _gtk_bubble_window_set_position (GtkBubbleWindow *window,
854 GtkPositionType position)
856 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
857 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
859 gtk_bubble_window_update_preferred_position (window, position);
861 if (gtk_widget_get_visible (GTK_WIDGET (window)))
862 gtk_bubble_window_update_position (window);
866 * gtk_bubble_window_get_position:
867 * @window: a #GtkBubbleWindow
869 * Returns the preferred position to place @window
871 * Returns: The preferred position
876 _gtk_bubble_window_get_position (GtkBubbleWindow *window)
878 GtkBubbleWindowPrivate *priv;
880 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP);
884 return priv->preferred_position;
888 * gtk_bubble_window_popup:
889 * @window: a #GtkBubbleWindow
890 * @relative_to: #GdkWindow to position upon
891 * @pointing_to: rectangle to point to, in @relative_to coordinates
892 * @position: preferred position for @window
894 * This function sets atomically all #GtkBubbleWindow position
895 * parameters, and shows/updates @window
900 _gtk_bubble_window_popup (GtkBubbleWindow *window,
901 GdkWindow *relative_to,
902 cairo_rectangle_int_t *pointing_to,
903 GtkPositionType position)
905 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
906 g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
907 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
908 g_return_if_fail (pointing_to != NULL);
910 gtk_bubble_window_update_preferred_position (window, position);
911 gtk_bubble_window_update_relative_to (window, relative_to);
912 gtk_bubble_window_update_pointing_to (window, pointing_to);
914 if (!gtk_widget_get_visible (GTK_WIDGET (window)))
915 gtk_widget_show (GTK_WIDGET (window));
917 gtk_bubble_window_update_position (window);
921 * gtk_bubble_window_popdown:
922 * @window: a #GtkBubbleWindow
924 * Removes the window from the screen
927 * If a grab was previously added through gtk_bubble_window_grab(),
928 * the grab will be removed by this function.
934 _gtk_bubble_window_popdown (GtkBubbleWindow *window)
936 GtkBubbleWindowPrivate *priv = window->priv;
938 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
941 _gtk_bubble_window_ungrab (window);
943 if (gtk_widget_get_visible (GTK_WIDGET (window)))
944 gtk_widget_hide (GTK_WIDGET (window));
948 * gtk_bubble_window_grab:
949 * @window: a #GtkBubbleWindow
950 * @device: a master #GdkDevice
951 * @activate_time: timestamp to perform the grab
953 * This function performs GDK and GTK+ grabs on @device and
954 * its paired #GdkDevice. After this call all pointer/keyboard
955 * events will be handled by @window.
957 * Calling this also brings in a #GtkMenu alike behavior, clicking
958 * outside the #GtkBubbleWindow or pressing the Escape key will
959 * popdown the menu by default.
962 * If there was a previous grab, it will be undone before doing
963 * the requested grab.
966 * Returns: %TRUE if the grab was successful
971 _gtk_bubble_window_grab (GtkBubbleWindow *window,
973 guint32 activate_time)
975 GtkBubbleWindowPrivate *priv;
976 GdkDevice *other_device;
977 GdkWindow *grab_window;
978 GdkGrabStatus status;
980 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
981 g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
982 g_return_val_if_fail (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER, FALSE);
986 if (!priv->has_pointing_to ||
987 gdk_window_is_destroyed (priv->relative_to))
991 _gtk_bubble_window_ungrab (window);
993 gtk_widget_realize (GTK_WIDGET (window));
994 grab_window = gtk_widget_get_window (GTK_WIDGET (window));
995 other_device = gdk_device_get_associated_device (device);
997 status = gdk_device_grab (device, grab_window,
998 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
999 NULL, activate_time);
1001 if (status == GDK_GRAB_SUCCESS)
1003 status = gdk_device_grab (other_device, grab_window,
1004 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1005 NULL, activate_time);
1007 /* Ungrab the first device on error */
1008 if (status != GDK_GRAB_SUCCESS)
1009 gdk_device_ungrab (device, activate_time);
1012 if (status == GDK_GRAB_SUCCESS)
1014 gtk_device_grab_add (GTK_WIDGET (window), device, TRUE);
1015 priv->device = device;
1018 return status == GDK_GRAB_SUCCESS;
1022 * gtk_bubble_window_ungrab:
1023 * @window: a #GtkBubbleWindow
1025 * This functions undoes a grab added through gtk_bubble_window_grab()
1031 _gtk_bubble_window_ungrab (GtkBubbleWindow *window)
1033 GtkBubbleWindowPrivate *priv;
1035 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1037 priv = window->priv;
1042 gdk_device_ungrab (priv->device, GDK_CURRENT_TIME);
1043 gdk_device_ungrab (gdk_device_get_associated_device (priv->device),
1045 gtk_device_grab_remove (GTK_WIDGET (window), priv->device);
1046 priv->device = NULL;