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);
98 gtk_style_context_add_class (gtk_widget_get_style_context (widget),
103 gtk_bubble_window_constructor (GType type,
104 guint n_construct_properties,
105 GObjectConstructParam *construct_properties)
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);
119 gtk_bubble_window_set_property (GObject *object,
126 case PROP_RELATIVE_TO:
127 _gtk_bubble_window_set_relative_to (GTK_BUBBLE_WINDOW (object),
128 g_value_get_object (value));
130 case PROP_POINTING_TO:
131 _gtk_bubble_window_set_pointing_to (GTK_BUBBLE_WINDOW (object),
132 g_value_get_boxed (value));
135 _gtk_bubble_window_set_position (GTK_BUBBLE_WINDOW (object),
136 g_value_get_enum (value));
139 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
144 gtk_bubble_window_get_property (GObject *object,
149 GtkBubbleWindowPrivate *priv = GTK_BUBBLE_WINDOW (object)->priv;
153 case PROP_RELATIVE_TO:
154 g_value_set_object (value, priv->relative_to);
156 case PROP_POINTING_TO:
157 g_value_set_boxed (value, &priv->pointing_to);
160 g_value_set_enum (value, priv->preferred_position);
163 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
168 gtk_bubble_window_finalize (GObject *object)
170 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (object);
171 GtkBubbleWindowPrivate *priv = window->priv;
173 _gtk_bubble_window_popdown (window);
175 if (priv->relative_to)
176 g_object_unref (priv->relative_to);
178 G_OBJECT_CLASS (_gtk_bubble_window_parent_class)->finalize (object);
182 gtk_bubble_window_get_pointed_to_coords (GtkBubbleWindow *window,
185 cairo_rectangle_int_t *root_rect)
187 GtkBubbleWindowPrivate *priv = window->priv;
188 cairo_rectangle_int_t rect;
191 rect = priv->pointing_to;
192 screen = gtk_widget_get_screen (GTK_WIDGET (window));
194 if (priv->relative_to)
195 gdk_window_get_root_coords (priv->relative_to,
196 rect.x, rect.y, &rect.x, &rect.y);
198 if (POS_IS_VERTICAL (priv->final_position))
200 *x = CLAMP (rect.x + (rect.width / 2),
201 0, gdk_screen_get_width (screen));
204 if (priv->final_position == GTK_POS_BOTTOM)
209 *y = CLAMP (rect.y + (rect.height / 2),
210 0, gdk_screen_get_height (screen));
213 if (priv->final_position == GTK_POS_RIGHT)
222 gtk_bubble_window_get_gap_coords (GtkBubbleWindow *window,
229 GtkPositionType *gap_side_out)
231 GtkBubbleWindowPrivate *priv = window->priv;
232 gint base, tip, x, y;
233 gint initial_x, initial_y;
235 gint final_x, final_y;
236 GtkPositionType gap_side;
237 GtkAllocation allocation;
239 gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL);
240 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
243 gap_side = GTK_POS_LEFT;
245 if (priv->final_position == GTK_POS_BOTTOM ||
246 priv->final_position == GTK_POS_RIGHT)
251 gap_side = (priv->final_position == GTK_POS_BOTTOM) ? GTK_POS_TOP : GTK_POS_LEFT;
253 else if (priv->final_position == GTK_POS_TOP)
255 base = allocation.height - TAIL_HEIGHT;
256 tip = allocation.height;
257 gap_side = GTK_POS_BOTTOM;
259 else if (priv->final_position == GTK_POS_LEFT)
261 base = allocation.width - TAIL_HEIGHT;
262 tip = allocation.width;
263 gap_side = GTK_POS_RIGHT;
266 if (POS_IS_VERTICAL (priv->final_position))
268 initial_x = CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2,
269 0, allocation.width - TAIL_GAP_WIDTH);
272 tip_x = CLAMP (x - priv->win_x, 0, allocation.width);
275 final_x = CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2,
276 TAIL_GAP_WIDTH, allocation.width);
282 initial_y = CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2,
283 0, allocation.height - TAIL_GAP_WIDTH);
286 tip_y = CLAMP (y - priv->win_y, 0, allocation.height);
289 final_y = CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2,
290 TAIL_GAP_WIDTH, allocation.height);
294 *initial_x_out = initial_x;
296 *initial_y_out = initial_y;
304 *final_x_out = final_x;
306 *final_y_out = final_y;
309 *gap_side_out = gap_side;
313 gtk_bubble_window_get_rect_coords (GtkBubbleWindow *window,
319 GtkBubbleWindowPrivate *priv = window->priv;
321 GtkAllocation allocation;
323 x1 = y1 = x2 = y2 = 0;
324 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
326 if (priv->final_position == GTK_POS_TOP)
330 x2 = allocation.width;
331 y2 = allocation.height - TAIL_HEIGHT;
333 else if (priv->final_position == GTK_POS_BOTTOM)
337 x2 = allocation.width;
338 y2 = allocation.height;
340 else if (priv->final_position == GTK_POS_LEFT)
344 x2 = allocation.width - TAIL_HEIGHT;
345 y2 = allocation.height;
347 else if (priv->final_position == GTK_POS_RIGHT)
351 x2 = allocation.width;
352 y2 = allocation.height;
366 gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window,
369 gint initial_x, initial_y;
371 gint final_x, final_y;
373 gtk_bubble_window_get_gap_coords (window,
374 &initial_x, &initial_y,
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);
385 gtk_bubble_window_apply_border_path (GtkBubbleWindow *window,
388 GtkBubbleWindowPrivate *priv;
389 GtkAllocation allocation;
393 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
395 gtk_bubble_window_apply_tail_path (window, cr);
396 gtk_bubble_window_get_rect_coords (window, &x1, &y1, &x2, &y2);
398 if (priv->final_position == GTK_POS_TOP)
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);
405 else if (priv->final_position == GTK_POS_BOTTOM)
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);
412 else if (priv->final_position == GTK_POS_LEFT)
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);
419 else if (priv->final_position == GTK_POS_RIGHT)
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);
427 cairo_close_path (cr);
431 gtk_bubble_window_update_shape (GtkBubbleWindow *window)
433 cairo_surface_t *surface;
434 cairo_region_t *region;
438 win = gtk_widget_get_window (GTK_WIDGET (window));
440 gdk_window_create_similar_surface (win,
441 CAIRO_CONTENT_COLOR_ALPHA,
442 gdk_window_get_width (win),
443 gdk_window_get_height (win));
445 cr = cairo_create (surface);
446 gtk_bubble_window_apply_border_path (window, cr);
450 region = gdk_cairo_region_create_from_surface (surface);
451 cairo_surface_destroy (surface);
453 if (!gtk_widget_is_composited (GTK_WIDGET (window)))
454 gtk_widget_shape_combine_region (GTK_WIDGET (window), region);
456 gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region);
457 cairo_region_destroy (region);
461 gtk_bubble_window_update_position (GtkBubbleWindow *window)
463 GtkBubbleWindowPrivate *priv;
464 cairo_rectangle_int_t rect;
465 GtkAllocation allocation;
466 gint win_x, win_y, x, y;
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;
475 gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect);
477 /* Check whether there's enough room on the
478 * preferred side, move to the opposite one if not.
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;
491 if (POS_IS_VERTICAL (priv->final_position))
493 win_x = CLAMP (x - allocation.width / 2,
494 0, gdk_screen_get_width (screen) - allocation.width);
497 if (priv->final_position == GTK_POS_TOP)
498 win_y -= allocation.height;
502 win_y = CLAMP (y - allocation.height / 2,
503 0, gdk_screen_get_height (screen) - allocation.height);
506 if (priv->final_position == GTK_POS_LEFT)
507 win_x -= allocation.width;
513 gtk_window_move (GTK_WINDOW (window), win_x, win_y);
515 gtk_widget_queue_resize (GTK_WIDGET (window));
519 gtk_bubble_window_draw (GtkWidget *widget,
522 GtkStyleContext *context;
523 GtkAllocation allocation;
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;
533 context = gtk_widget_get_style_context (widget);
534 state = gtk_widget_get_state_flags (widget);
535 gtk_widget_get_allocation (widget, &allocation);
537 if (gtk_widget_is_composited (widget))
540 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
541 cairo_set_source_rgba (cr, 0, 0, 0, 0);
546 gtk_bubble_window_get_rect_coords (GTK_BUBBLE_WINDOW (widget),
550 /* Render the rect background */
551 gtk_render_background (context, cr,
553 rect_x2 - rect_x1, rect_y2 - rect_y1);
555 gtk_bubble_window_get_gap_coords (GTK_BUBBLE_WINDOW (widget),
556 &initial_x, &initial_y,
561 if (POS_IS_VERTICAL (gap_side))
563 gap_start = initial_x;
568 gap_start = initial_y;
572 /* Now render the frame, without the gap for the arrow tip */
573 gtk_render_frame_gap (context, cr,
575 rect_x2 - rect_x1, rect_y2 - rect_y1,
579 /* Clip to the arrow shape */
582 gtk_bubble_window_apply_tail_path (GTK_BUBBLE_WINDOW (widget), cr);
585 /* Render the arrow background */
586 gtk_render_background (context, cr,
588 allocation.width, allocation.height);
590 /* Render the border of the arrow tip */
591 gtk_style_context_get_border (context, state, &border);
593 if (border.bottom > 0)
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);
599 cairo_set_line_width (cr, border.bottom);
606 child = gtk_bin_get_child (GTK_BIN (widget));
609 gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
615 get_padding_and_border (GtkWidget *widget,
618 GtkStyleContext *context;
622 context = gtk_widget_get_style_context (widget);
623 state = gtk_widget_get_state_flags (widget);
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;
634 gtk_bubble_window_get_preferred_width (GtkWidget *widget,
638 GtkBubbleWindowPrivate *priv;
643 priv = GTK_BUBBLE_WINDOW (widget)->priv;
644 child = gtk_bin_get_child (GTK_BIN (widget));
648 gtk_widget_get_preferred_width (child, &min, &nat);
650 get_padding_and_border (widget, &border);
651 min += border.left + border.right;
652 nat += border.left + border.right;
654 if (!POS_IS_VERTICAL (priv->final_position))
661 *minimum_width = MAX (min, TAIL_GAP_WIDTH);
664 *natural_width = MAX (nat, TAIL_GAP_WIDTH);
668 gtk_bubble_window_get_preferred_height (GtkWidget *widget,
669 gint *minimum_height,
670 gint *natural_height)
672 GtkBubbleWindowPrivate *priv;
677 priv = GTK_BUBBLE_WINDOW (widget)->priv;
678 child = gtk_bin_get_child (GTK_BIN (widget));
682 gtk_widget_get_preferred_height (child, &min, &nat);
684 get_padding_and_border (widget, &border);
685 min += border.top + border.bottom;
686 nat += border.top + border.bottom;
688 if (POS_IS_VERTICAL (priv->final_position))
695 *minimum_height = MAX (min, TAIL_GAP_WIDTH);
698 *natural_height = MAX (nat, TAIL_GAP_WIDTH);
702 gtk_bubble_window_size_allocate (GtkWidget *widget,
703 GtkAllocation *allocation)
705 GtkBubbleWindowPrivate *priv;
708 priv = GTK_BUBBLE_WINDOW (widget)->priv;
709 gtk_widget_set_allocation (widget, allocation);
710 child = gtk_bin_get_child (GTK_BIN (widget));
714 GtkAllocation child_alloc;
717 get_padding_and_border (widget, &border);
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;
724 if (POS_IS_VERTICAL (priv->final_position))
725 child_alloc.height -= TAIL_HEIGHT;
727 child_alloc.width -= TAIL_HEIGHT;
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;
734 gtk_widget_size_allocate (child, &child_alloc);
737 if (gtk_widget_get_realized (widget))
738 gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget));
740 if (gtk_widget_get_visible (widget))
741 gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget));
745 gtk_bubble_window_button_press (GtkWidget *widget,
746 GdkEventButton *event)
750 child = gtk_bin_get_child (GTK_BIN (widget));
752 if (child && event->window == gtk_widget_get_window (widget))
754 GtkAllocation child_alloc;
756 gtk_widget_get_allocation (child, &child_alloc);
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));
765 _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
767 return GDK_EVENT_PROPAGATE;
771 gtk_bubble_window_key_press (GtkWidget *widget,
774 if (event->keyval == GDK_KEY_Escape)
776 _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
777 return GDK_EVENT_STOP;
780 return GDK_EVENT_PROPAGATE;
784 gtk_bubble_window_grab_broken (GtkWidget *widget,
785 GdkEventGrabBroken *grab_broken)
787 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
788 GtkBubbleWindowPrivate *priv;
789 GdkDevice *event_device;
792 event_device = gdk_event_get_device ((GdkEvent *) grab_broken);
794 if (event_device == priv->device ||
795 event_device == gdk_device_get_associated_device (priv->device))
796 _gtk_bubble_window_ungrab (window);
802 gtk_bubble_window_grab_notify (GtkWidget *widget,
803 gboolean was_grabbed)
805 GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
806 GtkBubbleWindowPrivate *priv;
810 if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device))
811 _gtk_bubble_window_ungrab (window);
815 gtk_bubble_window_screen_changed (GtkWidget *widget,
816 GdkScreen *previous_screen)
821 screen = gtk_widget_get_screen (widget);
822 visual = gdk_screen_get_rgba_visual (screen);
825 gtk_widget_set_visual (widget, visual);
829 _gtk_bubble_window_class_init (GtkBubbleWindowClass *klass)
831 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
832 GObjectClass *object_class = G_OBJECT_CLASS (klass);
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;
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;
849 g_object_class_install_property (object_class,
851 g_param_spec_object ("relative-to",
853 P_("Window the bubble window points to"),
855 GTK_PARAM_READWRITE));
856 g_object_class_install_property (object_class,
858 g_param_spec_boxed ("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,
865 g_param_spec_enum ("position",
867 P_("Position to place the bubble window"),
868 GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
869 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
871 g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate));
875 gtk_bubble_window_update_relative_to (GtkBubbleWindow *window,
876 GdkWindow *relative_to)
878 GtkBubbleWindowPrivate *priv;
882 if (priv->relative_to == relative_to)
885 if (priv->relative_to)
886 g_object_unref (priv->relative_to);
888 priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL;
889 g_object_notify (G_OBJECT (window), "relative-to");
893 gtk_bubble_window_update_pointing_to (GtkBubbleWindow *window,
894 cairo_rectangle_int_t *pointing_to)
896 GtkBubbleWindowPrivate *priv;
899 priv->pointing_to = *pointing_to;
900 priv->has_pointing_to = TRUE;
901 g_object_notify (G_OBJECT (window), "pointing-to");
905 gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window,
906 GtkPositionType position)
908 GtkBubbleWindowPrivate *priv;
911 priv->preferred_position = position;
912 g_object_notify (G_OBJECT (window), "position");
916 * gtk_bubble_window_new:
918 * Returns a newly created #GtkBubblewindow
920 * Returns: a new #GtkBubbleWindow
925 _gtk_bubble_window_new (void)
927 return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL);
931 * gtk_bubble_window_set_relative_to:
932 * @window: a #GtkBubbleWindow
933 * @relative_to: a #GdkWindow
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()
939 * If @window is currently visible, it will be moved to reflect
945 _gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
946 GdkWindow *relative_to)
948 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
949 g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
951 gtk_bubble_window_update_relative_to (window, relative_to);
953 if (gtk_widget_get_visible (GTK_WIDGET (window)))
954 gtk_bubble_window_update_position (window);
958 * gtk_bubble_window_get_relative_to:
959 * @window: a #GtkBubbleWindow
961 * Returns the #GdkWindow used as the origin of coordinates.
962 * If @window is currently visible, it will be moved to reflect
965 * Returns: the #GdkWindow @window is placed upon
970 _gtk_bubble_window_get_relative_to (GtkBubbleWindow *window)
972 GtkBubbleWindowPrivate *priv;
974 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL);
978 return priv->relative_to;
982 * gtk_bubble_window_set_pointing_to:
983 * @window: a #GtkBubbleWindow
984 * @rect: rectangle to point to
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().
993 _gtk_bubble_window_set_pointing_to (GtkBubbleWindow *window,
994 cairo_rectangle_int_t *rect)
996 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
997 g_return_if_fail (rect != NULL);
999 gtk_bubble_window_update_pointing_to (window, rect);
1001 if (gtk_widget_get_visible (GTK_WIDGET (window)))
1002 gtk_bubble_window_update_position (window);
1006 * gtk_bubble_window_get_pointing_to:
1007 * @window: a #GtkBubbleWindow
1008 * @rect: (out): location to store the rectangle
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
1014 * Returns: %TRUE if a rectangle is set
1019 _gtk_bubble_window_get_pointing_to (GtkBubbleWindow *window,
1020 cairo_rectangle_int_t *rect)
1022 GtkBubbleWindowPrivate *priv;
1024 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
1026 priv = window->priv;
1029 *rect = priv->pointing_to;
1031 return priv->has_pointing_to;
1035 * gtk_bubble_window_set_position:
1036 * @window: a #GtkBubbleWindow
1037 * @position: preferred bubble position
1039 * Sets the preferred position for @window to appear.
1040 * If @window is currently visible, it will be moved to reflect
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
1052 _gtk_bubble_window_set_position (GtkBubbleWindow *window,
1053 GtkPositionType position)
1055 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1056 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
1058 gtk_bubble_window_update_preferred_position (window, position);
1060 if (gtk_widget_get_visible (GTK_WIDGET (window)))
1061 gtk_bubble_window_update_position (window);
1065 * gtk_bubble_window_get_position:
1066 * @window: a #GtkBubbleWindow
1068 * Returns the preferred position to place @window
1070 * Returns: The preferred position
1075 _gtk_bubble_window_get_position (GtkBubbleWindow *window)
1077 GtkBubbleWindowPrivate *priv;
1079 g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP);
1081 priv = window->priv;
1083 return priv->preferred_position;
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
1093 * This function sets atomically all #GtkBubbleWindow position
1094 * parameters, and shows/updates @window
1099 _gtk_bubble_window_popup (GtkBubbleWindow *window,
1100 GdkWindow *relative_to,
1101 cairo_rectangle_int_t *pointing_to,
1102 GtkPositionType position)
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);
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);
1113 if (!gtk_widget_get_visible (GTK_WIDGET (window)))
1114 gtk_widget_show (GTK_WIDGET (window));
1116 gtk_bubble_window_update_position (window);
1120 * gtk_bubble_window_popdown:
1121 * @window: a #GtkBubbleWindow
1123 * Removes the window from the screen
1126 * If a grab was previously added through gtk_bubble_window_grab(),
1127 * the grab will be removed by this function.
1133 _gtk_bubble_window_popdown (GtkBubbleWindow *window)
1135 GtkBubbleWindowPrivate *priv = window->priv;
1137 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1140 _gtk_bubble_window_ungrab (window);
1142 if (gtk_widget_get_visible (GTK_WIDGET (window)))
1143 gtk_widget_hide (GTK_WIDGET (window));
1147 * gtk_bubble_window_grab:
1148 * @window: a #GtkBubbleWindow
1149 * @device: a master #GdkDevice
1150 * @activate_time: timestamp to perform the grab
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.
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.
1161 * If there was a previous grab, it will be undone before doing
1162 * the requested grab.
1165 * Returns: %TRUE if the grab was successful
1170 _gtk_bubble_window_grab (GtkBubbleWindow *window,
1172 guint32 activate_time)
1174 GtkBubbleWindowPrivate *priv;
1175 GdkDevice *other_device;
1176 GdkWindow *grab_window;
1177 GdkGrabStatus status;
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);
1183 priv = window->priv;
1185 if (!priv->has_pointing_to ||
1186 gdk_window_is_destroyed (priv->relative_to))
1190 _gtk_bubble_window_ungrab (window);
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);
1196 status = gdk_device_grab (device, grab_window,
1197 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1198 NULL, activate_time);
1200 if (status == GDK_GRAB_SUCCESS)
1202 status = gdk_device_grab (other_device, grab_window,
1203 GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
1204 NULL, activate_time);
1206 /* Ungrab the first device on error */
1207 if (status != GDK_GRAB_SUCCESS)
1208 gdk_device_ungrab (device, activate_time);
1211 if (status == GDK_GRAB_SUCCESS)
1213 gtk_device_grab_add (GTK_WIDGET (window), device, TRUE);
1214 priv->device = device;
1217 return status == GDK_GRAB_SUCCESS;
1221 * gtk_bubble_window_ungrab:
1222 * @window: a #GtkBubbleWindow
1224 * This functions undoes a grab added through gtk_bubble_window_grab()
1230 _gtk_bubble_window_ungrab (GtkBubbleWindow *window)
1232 GtkBubbleWindowPrivate *priv;
1234 g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
1236 priv = window->priv;
1241 gdk_device_ungrab (priv->device, GDK_CURRENT_TIME);
1242 gdk_device_ungrab (gdk_device_get_associated_device (priv->device),
1244 gtk_device_grab_remove (GTK_WIDGET (window), priv->device);
1245 priv->device = NULL;