/* GTK - The GIMP Toolkit * Copyright © 2013 Carlos Garnacho * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ /* * GtkBubbleWindow is a bubble-like context window, primarily mean for * context-dependent helpers on touch interfaces. * * In order to place a GtkBubbleWindow to point to some other area, * use gtk_bubble_window_set_relative_to(), gtk_bubble_window_set_pointing_to() * and gtk_bubble_window_set_position(). Although it is usually more * convenient to use gtk_bubble_window_popup() which handles all of those * at once. * * By default, no grabs are performed on the GtkBubbleWindow, leaving * the popup/popdown semantics up to the caller, gtk_bubble_window_grab() * can be used to grab the window for a device pair, bringing #GtkMenu alike * popdown behavior by default on keyboard/pointer interaction. Grabs need * to be undone through gtk_bubble_window_ungrab(). */ #include "config.h" #include #include #include "gtkbubblewindowprivate.h" #include "gtktypebuiltins.h" #include "gtkmain.h" #include "gtkprivate.h" #include "gtkintl.h" #define TAIL_GAP_WIDTH 24 #define TAIL_HEIGHT 12 #define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM) #define GRAB_EVENT_MASK \ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | \ GDK_POINTER_MOTION_MASK typedef struct _GtkBubbleWindowPrivate GtkBubbleWindowPrivate; enum { PROP_RELATIVE_TO = 1, PROP_POINTING_TO, PROP_POSITION }; struct _GtkBubbleWindowPrivate { GdkDevice *device; GdkWindow *relative_to; cairo_rectangle_int_t pointing_to; gint win_x; gint win_y; guint has_pointing_to : 1; guint grabbed : 1; guint preferred_position : 2; guint final_position : 2; }; G_DEFINE_TYPE (GtkBubbleWindow, _gtk_bubble_window, GTK_TYPE_WINDOW) static void _gtk_bubble_window_init (GtkBubbleWindow *window) { GtkWidget *widget; GdkScreen *screen; GdkVisual *visual; widget = GTK_WIDGET (window); window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window, GTK_TYPE_BUBBLE_WINDOW, GtkBubbleWindowPrivate); gtk_window_set_default_size (GTK_WINDOW (window), TAIL_GAP_WIDTH, TAIL_GAP_WIDTH); gtk_widget_set_app_paintable (widget, TRUE); screen = gtk_widget_get_screen (widget); visual = gdk_screen_get_rgba_visual (screen); if (visual) gtk_widget_set_visual (widget, visual); gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_OSD); } static GObject * gtk_bubble_window_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; object = G_OBJECT_CLASS (_gtk_bubble_window_parent_class)->constructor (type, n_construct_properties, construct_properties); g_object_set (object, "type", GTK_WINDOW_POPUP, NULL); return object; } static void gtk_bubble_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_RELATIVE_TO: _gtk_bubble_window_set_relative_to (GTK_BUBBLE_WINDOW (object), g_value_get_object (value)); break; case PROP_POINTING_TO: _gtk_bubble_window_set_pointing_to (GTK_BUBBLE_WINDOW (object), g_value_get_boxed (value)); break; case PROP_POSITION: _gtk_bubble_window_set_position (GTK_BUBBLE_WINDOW (object), g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_bubble_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkBubbleWindowPrivate *priv = GTK_BUBBLE_WINDOW (object)->priv; switch (prop_id) { case PROP_RELATIVE_TO: g_value_set_object (value, priv->relative_to); break; case PROP_POINTING_TO: g_value_set_boxed (value, &priv->pointing_to); break; case PROP_POSITION: g_value_set_enum (value, priv->preferred_position); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_bubble_window_finalize (GObject *object) { GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (object); GtkBubbleWindowPrivate *priv = window->priv; _gtk_bubble_window_popdown (window); if (priv->relative_to) g_object_unref (priv->relative_to); G_OBJECT_CLASS (_gtk_bubble_window_parent_class)->finalize (object); } static void gtk_bubble_window_get_pointed_to_coords (GtkBubbleWindow *window, gint *x, gint *y, cairo_rectangle_int_t *root_rect) { GtkBubbleWindowPrivate *priv = window->priv; cairo_rectangle_int_t rect; GdkScreen *screen; rect = priv->pointing_to; screen = gtk_widget_get_screen (GTK_WIDGET (window)); if (priv->relative_to) gdk_window_get_root_coords (priv->relative_to, rect.x, rect.y, &rect.x, &rect.y); if (POS_IS_VERTICAL (priv->final_position)) { *x = CLAMP (rect.x + (rect.width / 2), 0, gdk_screen_get_width (screen)); *y = rect.y; if (priv->final_position == GTK_POS_BOTTOM) (*y) += rect.height; } else { *y = CLAMP (rect.y + (rect.height / 2), 0, gdk_screen_get_height (screen)); *x = rect.x; if (priv->final_position == GTK_POS_RIGHT) (*x) += rect.width; } if (root_rect) *root_rect = rect; } static void gtk_bubble_window_get_gap_coords (GtkBubbleWindow *window, gint *initial_x_out, gint *initial_y_out, gint *tip_x_out, gint *tip_y_out, gint *final_x_out, gint *final_y_out, GtkPositionType *gap_side_out) { GtkBubbleWindowPrivate *priv = window->priv; gint base, tip, x, y; gint initial_x, initial_y; gint tip_x, tip_y; gint final_x, final_y; GtkPositionType gap_side; GtkAllocation allocation; gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL); gtk_widget_get_allocation (GTK_WIDGET (window), &allocation); base = tip = 0; gap_side = GTK_POS_LEFT; if (priv->final_position == GTK_POS_BOTTOM || priv->final_position == GTK_POS_RIGHT) { base = TAIL_HEIGHT; tip = 0; gap_side = (priv->final_position == GTK_POS_BOTTOM) ? GTK_POS_TOP : GTK_POS_LEFT; } else if (priv->final_position == GTK_POS_TOP) { base = allocation.height - TAIL_HEIGHT; tip = allocation.height; gap_side = GTK_POS_BOTTOM; } else if (priv->final_position == GTK_POS_LEFT) { base = allocation.width - TAIL_HEIGHT; tip = allocation.width; gap_side = GTK_POS_RIGHT; } if (POS_IS_VERTICAL (priv->final_position)) { initial_x = CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2, 0, allocation.width - TAIL_GAP_WIDTH); initial_y = base; tip_x = CLAMP (x - priv->win_x, 0, allocation.width); tip_y = tip; final_x = CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2, TAIL_GAP_WIDTH, allocation.width); final_y = base; } else { initial_x = base; initial_y = CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2, 0, allocation.height - TAIL_GAP_WIDTH); tip_x = tip; tip_y = CLAMP (y - priv->win_y, 0, allocation.height); final_x = base; final_y = CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2, TAIL_GAP_WIDTH, allocation.height); } if (initial_x_out) *initial_x_out = initial_x; if (initial_y_out) *initial_y_out = initial_y; if (tip_x_out) *tip_x_out = tip_x; if (tip_y_out) *tip_y_out = tip_y; if (final_x_out) *final_x_out = final_x; if (final_y_out) *final_y_out = final_y; if (gap_side_out) *gap_side_out = gap_side; } static void gtk_bubble_window_get_rect_coords (GtkBubbleWindow *window, gint *x1_out, gint *y1_out, gint *x2_out, gint *y2_out) { GtkBubbleWindowPrivate *priv = window->priv; gint x1, x2, y1, y2; GtkAllocation allocation; x1 = y1 = x2 = y2 = 0; gtk_widget_get_allocation (GTK_WIDGET (window), &allocation); if (priv->final_position == GTK_POS_TOP) { x1 = 0; y1 = 0; x2 = allocation.width; y2 = allocation.height - TAIL_HEIGHT; } else if (priv->final_position == GTK_POS_BOTTOM) { x1 = 0; y1 = TAIL_HEIGHT; x2 = allocation.width; y2 = allocation.height; } else if (priv->final_position == GTK_POS_LEFT) { x1 = 0; y1 = 0; x2 = allocation.width - TAIL_HEIGHT; y2 = allocation.height; } else if (priv->final_position == GTK_POS_RIGHT) { x1 = TAIL_HEIGHT; y1 = 0; x2 = allocation.width; y2 = allocation.height; } if (x1_out) *x1_out = x1; if (y1_out) *y1_out = y1; if (x2_out) *x2_out = x2; if (y2_out) *y2_out = y2; } static void gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window, cairo_t *cr) { gint initial_x, initial_y; gint tip_x, tip_y; gint final_x, final_y; gtk_bubble_window_get_gap_coords (window, &initial_x, &initial_y, &tip_x, &tip_y, &final_x, &final_y, NULL); cairo_move_to (cr, initial_x, initial_y); cairo_line_to (cr, tip_x, tip_y); cairo_line_to (cr, final_x, final_y); } static void gtk_bubble_window_apply_border_path (GtkBubbleWindow *window, cairo_t *cr) { GtkBubbleWindowPrivate *priv; GtkAllocation allocation; gint x1, y1, x2, y2; priv = window->priv; gtk_widget_get_allocation (GTK_WIDGET (window), &allocation); gtk_bubble_window_apply_tail_path (window, cr); gtk_bubble_window_get_rect_coords (window, &x1, &y1, &x2, &y2); if (priv->final_position == GTK_POS_TOP) { cairo_line_to (cr, x2, y2); cairo_line_to (cr, x2, y1); cairo_line_to (cr, x1, y1); cairo_line_to (cr, x1, y2); } else if (priv->final_position == GTK_POS_BOTTOM) { cairo_line_to (cr, x2, y1); cairo_line_to (cr, x2, y2); cairo_line_to (cr, x1, y2); cairo_line_to (cr, x1, y1); } else if (priv->final_position == GTK_POS_LEFT) { cairo_line_to (cr, x2, y2); cairo_line_to (cr, x1, y2); cairo_line_to (cr, x1, y1); cairo_line_to (cr, x2, y1); } else if (priv->final_position == GTK_POS_RIGHT) { cairo_line_to (cr, x1, y1); cairo_line_to (cr, x2, y1); cairo_line_to (cr, x2, y2); cairo_line_to (cr, x1, y2); } cairo_close_path (cr); } static void gtk_bubble_window_update_shape (GtkBubbleWindow *window) { cairo_surface_t *surface; cairo_region_t *region; GdkWindow *win; cairo_t *cr; win = gtk_widget_get_window (GTK_WIDGET (window)); surface = gdk_window_create_similar_surface (win, CAIRO_CONTENT_COLOR_ALPHA, gdk_window_get_width (win), gdk_window_get_height (win)); cr = cairo_create (surface); gtk_bubble_window_apply_border_path (window, cr); cairo_fill (cr); cairo_destroy (cr); region = gdk_cairo_region_create_from_surface (surface); cairo_surface_destroy (surface); if (!gtk_widget_is_composited (GTK_WIDGET (window))) gtk_widget_shape_combine_region (GTK_WIDGET (window), region); gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region); cairo_region_destroy (region); } static void gtk_bubble_window_update_position (GtkBubbleWindow *window) { GtkBubbleWindowPrivate *priv; cairo_rectangle_int_t rect; GtkAllocation allocation; gint win_x, win_y, x, y; GdkScreen *screen; priv = window->priv; screen = gtk_widget_get_screen (GTK_WIDGET (window)); gtk_widget_get_allocation (GTK_WIDGET (window), &allocation); priv->final_position = priv->preferred_position; rect = priv->pointing_to; gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect); /* Check whether there's enough room on the * preferred side, move to the opposite one if not. */ if (priv->preferred_position == GTK_POS_TOP && rect.y < allocation.height) priv->final_position = GTK_POS_BOTTOM; else if (priv->preferred_position == GTK_POS_BOTTOM && rect.y > gdk_screen_get_height (screen) - allocation.height) priv->final_position = GTK_POS_TOP; else if (priv->preferred_position == GTK_POS_LEFT && rect.x < allocation.width) priv->final_position = GTK_POS_RIGHT; else if (priv->preferred_position == GTK_POS_RIGHT && rect.x > gdk_screen_get_width (screen) - allocation.width) priv->final_position = GTK_POS_LEFT; if (POS_IS_VERTICAL (priv->final_position)) { win_x = CLAMP (x - allocation.width / 2, 0, gdk_screen_get_width (screen) - allocation.width); win_y = y; if (priv->final_position == GTK_POS_TOP) win_y -= allocation.height; } else { win_y = CLAMP (y - allocation.height / 2, 0, gdk_screen_get_height (screen) - allocation.height); win_x = x; if (priv->final_position == GTK_POS_LEFT) win_x -= allocation.width; } priv->win_x = win_x; priv->win_y = win_y; gtk_window_move (GTK_WINDOW (window), win_x, win_y); gtk_widget_queue_resize (GTK_WIDGET (window)); } static gboolean gtk_bubble_window_draw (GtkWidget *widget, cairo_t *cr) { GtkStyleContext *context; GtkAllocation allocation; GtkWidget *child; GtkBorder border; GdkRGBA border_color; gint rect_x1, rect_x2, rect_y1, rect_y2; gint initial_x, initial_y, final_x, final_y; gint gap_start, gap_end; GtkPositionType gap_side; GtkStateFlags state; context = gtk_widget_get_style_context (widget); state = gtk_widget_get_state_flags (widget); gtk_widget_get_allocation (widget, &allocation); if (gtk_widget_is_composited (widget)) { cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba (cr, 0, 0, 0, 0); cairo_paint (cr); cairo_restore (cr); } gtk_bubble_window_get_rect_coords (GTK_BUBBLE_WINDOW (widget), &rect_x1, &rect_y1, &rect_x2, &rect_y2); /* Render the rect background */ gtk_render_background (context, cr, rect_x1, rect_y1, rect_x2 - rect_x1, rect_y2 - rect_y1); gtk_bubble_window_get_gap_coords (GTK_BUBBLE_WINDOW (widget), &initial_x, &initial_y, NULL, NULL, &final_x, &final_y, &gap_side); if (POS_IS_VERTICAL (gap_side)) { gap_start = initial_x; gap_end = final_x; } else { gap_start = initial_y; gap_end = final_y; } /* Now render the frame, without the gap for the arrow tip */ gtk_render_frame_gap (context, cr, rect_x1, rect_y1, rect_x2 - rect_x1, rect_y2 - rect_y1, gap_side, gap_start, gap_end); /* Clip to the arrow shape */ cairo_save (cr); gtk_bubble_window_apply_tail_path (GTK_BUBBLE_WINDOW (widget), cr); cairo_clip (cr); /* Render the arrow background */ gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height); /* Render the border of the arrow tip */ gtk_style_context_get_border (context, state, &border); if (border.bottom > 0) { gtk_style_context_get_border_color (context, state, &border_color); gtk_bubble_window_apply_tail_path (GTK_BUBBLE_WINDOW (widget), cr); gdk_cairo_set_source_rgba (cr, &border_color); cairo_set_line_width (cr, border.bottom); cairo_stroke (cr); } /* We're done */ cairo_restore (cr); child = gtk_bin_get_child (GTK_BIN (widget)); if (child) gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr); return TRUE; } static void get_padding_and_border (GtkWidget *widget, GtkBorder *border) { GtkStyleContext *context; GtkStateFlags state; GtkBorder tmp; context = gtk_widget_get_style_context (widget); state = gtk_widget_get_state_flags (widget); gtk_style_context_get_padding (context, state, border); gtk_style_context_get_border (context, state, &tmp); border->top += tmp.top; border->right += tmp.right; border->bottom += tmp.bottom; border->left += tmp.left; } static void gtk_bubble_window_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width) { GtkBubbleWindowPrivate *priv; GtkWidget *child; gint min, nat; GtkBorder border; priv = GTK_BUBBLE_WINDOW (widget)->priv; child = gtk_bin_get_child (GTK_BIN (widget)); min = nat = 0; if (child) gtk_widget_get_preferred_width (child, &min, &nat); get_padding_and_border (widget, &border); min += border.left + border.right; nat += border.left + border.right; if (!POS_IS_VERTICAL (priv->final_position)) { min += TAIL_HEIGHT; nat += TAIL_HEIGHT; } if (minimum_width) *minimum_width = MAX (min, TAIL_GAP_WIDTH); if (natural_width) *natural_width = MAX (nat, TAIL_GAP_WIDTH); } static void gtk_bubble_window_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) { GtkBubbleWindowPrivate *priv; GtkWidget *child; gint min, nat; GtkBorder border; priv = GTK_BUBBLE_WINDOW (widget)->priv; child = gtk_bin_get_child (GTK_BIN (widget)); min = nat = 0; if (child) gtk_widget_get_preferred_height (child, &min, &nat); get_padding_and_border (widget, &border); min += border.top + border.bottom; nat += border.top + border.bottom; if (POS_IS_VERTICAL (priv->final_position)) { min += TAIL_HEIGHT; nat += TAIL_HEIGHT; } if (minimum_height) *minimum_height = MAX (min, TAIL_GAP_WIDTH); if (natural_height) *natural_height = MAX (nat, TAIL_GAP_WIDTH); } static void gtk_bubble_window_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkBubbleWindowPrivate *priv; GtkWidget *child; priv = GTK_BUBBLE_WINDOW (widget)->priv; gtk_widget_set_allocation (widget, allocation); child = gtk_bin_get_child (GTK_BIN (widget)); if (child) { GtkAllocation child_alloc; GtkBorder border; get_padding_and_border (widget, &border); child_alloc.x = border.left; child_alloc.y = border.top; child_alloc.width = allocation->width - border.left - border.right; child_alloc.height = allocation->height - border.top - border.bottom; if (POS_IS_VERTICAL (priv->final_position)) child_alloc.height -= TAIL_HEIGHT; else child_alloc.width -= TAIL_HEIGHT; if (priv->final_position == GTK_POS_BOTTOM) child_alloc.y += TAIL_HEIGHT; else if (priv->final_position == GTK_POS_RIGHT) child_alloc.x += TAIL_HEIGHT; gtk_widget_size_allocate (child, &child_alloc); } if (gtk_widget_get_realized (widget)) gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget)); if (gtk_widget_get_visible (widget)) gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget)); } static gboolean gtk_bubble_window_button_press (GtkWidget *widget, GdkEventButton *event) { GtkWidget *child; child = gtk_bin_get_child (GTK_BIN (widget)); if (child && event->window == gtk_widget_get_window (widget)) { GtkAllocation child_alloc; gtk_widget_get_allocation (child, &child_alloc); if (event->x < child_alloc.x || event->x > child_alloc.x + child_alloc.width || event->y < child_alloc.y || event->y > child_alloc.y + child_alloc.height) _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget)); } else _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget)); return GDK_EVENT_PROPAGATE; } static gboolean gtk_bubble_window_key_press (GtkWidget *widget, GdkEventKey *event) { if (event->keyval == GDK_KEY_Escape) { _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget)); return GDK_EVENT_STOP; } return GDK_EVENT_PROPAGATE; } static gboolean gtk_bubble_window_grab_broken (GtkWidget *widget, GdkEventGrabBroken *grab_broken) { GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget); GtkBubbleWindowPrivate *priv; GdkDevice *event_device; priv = window->priv; event_device = gdk_event_get_device ((GdkEvent *) grab_broken); if (event_device == priv->device || event_device == gdk_device_get_associated_device (priv->device)) _gtk_bubble_window_ungrab (window); return FALSE; } static void gtk_bubble_window_grab_notify (GtkWidget *widget, gboolean was_grabbed) { GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget); GtkBubbleWindowPrivate *priv; priv = window->priv; if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device)) _gtk_bubble_window_ungrab (window); } static void gtk_bubble_window_screen_changed (GtkWidget *widget, GdkScreen *previous_screen) { GdkScreen *screen; GdkVisual *visual; screen = gtk_widget_get_screen (widget); visual = gdk_screen_get_rgba_visual (screen); if (visual) gtk_widget_set_visual (widget, visual); } static void _gtk_bubble_window_class_init (GtkBubbleWindowClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructor = gtk_bubble_window_constructor; object_class->set_property = gtk_bubble_window_set_property; object_class->get_property = gtk_bubble_window_get_property; object_class->finalize = gtk_bubble_window_finalize; widget_class->get_preferred_width = gtk_bubble_window_get_preferred_width; widget_class->get_preferred_height = gtk_bubble_window_get_preferred_height; widget_class->size_allocate = gtk_bubble_window_size_allocate; widget_class->draw = gtk_bubble_window_draw; widget_class->button_press_event = gtk_bubble_window_button_press; widget_class->key_press_event = gtk_bubble_window_key_press; widget_class->grab_broken_event = gtk_bubble_window_grab_broken; widget_class->grab_notify = gtk_bubble_window_grab_notify; widget_class->screen_changed = gtk_bubble_window_screen_changed; g_object_class_install_property (object_class, PROP_RELATIVE_TO, g_param_spec_object ("relative-to", P_("Relative to"), P_("Window the bubble window points to"), GDK_TYPE_WINDOW, GTK_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_POINTING_TO, g_param_spec_boxed ("pointing-to", P_("Pointing to"), P_("Rectangle the bubble window points to"), CAIRO_GOBJECT_TYPE_RECTANGLE_INT, GTK_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_POSITION, g_param_spec_enum ("position", P_("Position"), P_("Position to place the bubble window"), GTK_TYPE_POSITION_TYPE, GTK_POS_TOP, GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate)); } static void gtk_bubble_window_update_relative_to (GtkBubbleWindow *window, GdkWindow *relative_to) { GtkBubbleWindowPrivate *priv; priv = window->priv; if (priv->relative_to == relative_to) return; if (priv->relative_to) g_object_unref (priv->relative_to); priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL; g_object_notify (G_OBJECT (window), "relative-to"); } static void gtk_bubble_window_update_pointing_to (GtkBubbleWindow *window, cairo_rectangle_int_t *pointing_to) { GtkBubbleWindowPrivate *priv; priv = window->priv; priv->pointing_to = *pointing_to; priv->has_pointing_to = TRUE; g_object_notify (G_OBJECT (window), "pointing-to"); } static void gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window, GtkPositionType position) { GtkBubbleWindowPrivate *priv; priv = window->priv; priv->preferred_position = position; g_object_notify (G_OBJECT (window), "position"); } /* * gtk_bubble_window_new: * * Returns a newly created #GtkBubblewindow * * Returns: a new #GtkBubbleWindow * * Since: 3.8 */ GtkWidget * _gtk_bubble_window_new (void) { return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL); } /* * gtk_bubble_window_set_relative_to: * @window: a #GtkBubbleWindow * @relative_to: a #GdkWindow * * Sets the #GdkWindow to act as the origin of coordinates of * @window, or %NULL to use the root window. See * gtk_bubble_window_set_pointing_to() * * If @window is currently visible, it will be moved to reflect * this change. * * Since: 3.8 */ void _gtk_bubble_window_set_relative_to (GtkBubbleWindow *window, GdkWindow *relative_to) { g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window)); g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to)); gtk_bubble_window_update_relative_to (window, relative_to); if (gtk_widget_get_visible (GTK_WIDGET (window))) gtk_bubble_window_update_position (window); } /* * gtk_bubble_window_get_relative_to: * @window: a #GtkBubbleWindow * * Returns the #GdkWindow used as the origin of coordinates. * If @window is currently visible, it will be moved to reflect * this change. * * Returns: the #GdkWindow @window is placed upon * * Since: 3.8 */ GdkWindow * _gtk_bubble_window_get_relative_to (GtkBubbleWindow *window) { GtkBubbleWindowPrivate *priv; g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL); priv = window->priv; return priv->relative_to; } /* * gtk_bubble_window_set_pointing_to: * @window: a #GtkBubbleWindow * @rect: rectangle to point to * * Sets the rectangle that @window will point to, the coordinates * of this rectangle are relative to the #GdkWindow set through * gtk_bubble_window_set_relative_to(). * * Since: 3.8 */ void _gtk_bubble_window_set_pointing_to (GtkBubbleWindow *window, cairo_rectangle_int_t *rect) { g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window)); g_return_if_fail (rect != NULL); gtk_bubble_window_update_pointing_to (window, rect); if (gtk_widget_get_visible (GTK_WIDGET (window))) gtk_bubble_window_update_position (window); } /* * gtk_bubble_window_get_pointing_to: * @window: a #GtkBubbleWindow * @rect: (out): location to store the rectangle * * If a rectangle to point to is set, this function will return * %TRUE and fill in @rect with the rectangle @window is currently * pointing to. * * Returns: %TRUE if a rectangle is set * * Since: 3.8 */ gboolean _gtk_bubble_window_get_pointing_to (GtkBubbleWindow *window, cairo_rectangle_int_t *rect) { GtkBubbleWindowPrivate *priv; g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE); priv = window->priv; if (rect) *rect = priv->pointing_to; return priv->has_pointing_to; } /* * gtk_bubble_window_set_position: * @window: a #GtkBubbleWindow * @position: preferred bubble position * * Sets the preferred position for @window to appear. * If @window is currently visible, it will be moved to reflect * this change. * * * This preference will be respected where possible, although * on lack of space (eg. if close to the screen edges), the * #GtkBubbleWindow may choose to appear on the opposite side * * * Since: 3.8 */ void _gtk_bubble_window_set_position (GtkBubbleWindow *window, GtkPositionType position) { g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window)); g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM); gtk_bubble_window_update_preferred_position (window, position); if (gtk_widget_get_visible (GTK_WIDGET (window))) gtk_bubble_window_update_position (window); } /* * gtk_bubble_window_get_position: * @window: a #GtkBubbleWindow * * Returns the preferred position to place @window * * Returns: The preferred position * * Since: 3.8 */ GtkPositionType _gtk_bubble_window_get_position (GtkBubbleWindow *window) { GtkBubbleWindowPrivate *priv; g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP); priv = window->priv; return priv->preferred_position; } /* * gtk_bubble_window_popup: * @window: a #GtkBubbleWindow * @relative_to: #GdkWindow to position upon * @pointing_to: rectangle to point to, in @relative_to coordinates * @position: preferred position for @window * * This function sets atomically all #GtkBubbleWindow position * parameters, and shows/updates @window * * Since: 3.8 */ void _gtk_bubble_window_popup (GtkBubbleWindow *window, GdkWindow *relative_to, cairo_rectangle_int_t *pointing_to, GtkPositionType position) { g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window)); g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to)); g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM); g_return_if_fail (pointing_to != NULL); gtk_bubble_window_update_preferred_position (window, position); gtk_bubble_window_update_relative_to (window, relative_to); gtk_bubble_window_update_pointing_to (window, pointing_to); if (!gtk_widget_get_visible (GTK_WIDGET (window))) gtk_widget_show (GTK_WIDGET (window)); gtk_bubble_window_update_position (window); } /* * gtk_bubble_window_popdown: * @window: a #GtkBubbleWindow * * Removes the window from the screen * * * If a grab was previously added through gtk_bubble_window_grab(), * the grab will be removed by this function. * * * Since: 3.8 */ void _gtk_bubble_window_popdown (GtkBubbleWindow *window) { GtkBubbleWindowPrivate *priv = window->priv; g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window)); if (priv->grabbed) _gtk_bubble_window_ungrab (window); if (gtk_widget_get_visible (GTK_WIDGET (window))) gtk_widget_hide (GTK_WIDGET (window)); } /* * gtk_bubble_window_grab: * @window: a #GtkBubbleWindow * @device: a master #GdkDevice * @activate_time: timestamp to perform the grab * * This function performs GDK and GTK+ grabs on @device and * its paired #GdkDevice. After this call all pointer/keyboard * events will be handled by @window. * * Calling this also brings in a #GtkMenu alike behavior, clicking * outside the #GtkBubbleWindow or pressing the Escape key will * popdown the menu by default. * * * If there was a previous grab, it will be undone before doing * the requested grab. * * * Returns: %TRUE if the grab was successful * * Since: 3.8 */ gboolean _gtk_bubble_window_grab (GtkBubbleWindow *window, GdkDevice *device, guint32 activate_time) { GtkBubbleWindowPrivate *priv; GdkDevice *other_device; GdkWindow *grab_window; GdkGrabStatus status; g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE); g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE); g_return_val_if_fail (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER, FALSE); priv = window->priv; if (!priv->has_pointing_to || gdk_window_is_destroyed (priv->relative_to)) return FALSE; if (priv->device) _gtk_bubble_window_ungrab (window); gtk_widget_realize (GTK_WIDGET (window)); grab_window = gtk_widget_get_window (GTK_WIDGET (window)); other_device = gdk_device_get_associated_device (device); status = gdk_device_grab (device, grab_window, GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK, NULL, activate_time); if (status == GDK_GRAB_SUCCESS) { status = gdk_device_grab (other_device, grab_window, GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK, NULL, activate_time); /* Ungrab the first device on error */ if (status != GDK_GRAB_SUCCESS) gdk_device_ungrab (device, activate_time); } if (status == GDK_GRAB_SUCCESS) { gtk_device_grab_add (GTK_WIDGET (window), device, TRUE); priv->device = device; } return status == GDK_GRAB_SUCCESS; } /* * gtk_bubble_window_ungrab: * @window: a #GtkBubbleWindow * * This functions undoes a grab added through gtk_bubble_window_grab() * in this @window, * * Since: 3.8 */ void _gtk_bubble_window_ungrab (GtkBubbleWindow *window) { GtkBubbleWindowPrivate *priv; g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window)); priv = window->priv; if (!priv->device) return; gdk_device_ungrab (priv->device, GDK_CURRENT_TIME); gdk_device_ungrab (gdk_device_get_associated_device (priv->device), GDK_CURRENT_TIME); gtk_device_grab_remove (GTK_WIDGET (window), priv->device); priv->device = NULL; }