X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktooltip.c;h=aba9ffb38a173b8be9a9a377bc19c76c4c9f05d2;hb=a89d420270d1a856e072ed87c365b0176f102e6c;hp=46ca8179abe3b42f884a4228e2f486eb8b4bf8b5;hpb=f6235caef968e92d698468d77a4e93f33a63855a;p=~andy%2Fgtk diff --git a/gtk/gtktooltip.c b/gtk/gtktooltip.c index 46ca8179a..aba9ffb38 100644 --- a/gtk/gtktooltip.c +++ b/gtk/gtktooltip.c @@ -14,24 +14,98 @@ * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ -#include +#include "config.h" + #include "gtktooltip.h" + +#include +#include + #include "gtkintl.h" #include "gtkwindow.h" #include "gtkmain.h" #include "gtklabel.h" #include "gtkimage.h" -#include "gtkhbox.h" -#include "gtkalignment.h" +#include "gtkbox.h" +#include "gtksettings.h" +#include "gtksizerequest.h" +#include "gtkstylecontext.h" +#include "gtkwindowprivate.h" -#include "gtkalias.h" -#include +#ifdef GDK_WINDOWING_WAYLAND +#include "wayland/gdkwayland.h" +#endif + + +/** + * SECTION:gtktooltip + * @Short_description: Add tips to your widgets + * @Title: GtkTooltip + * + * Basic tooltips can be realized simply by using gtk_widget_set_tooltip_text() + * or gtk_widget_set_tooltip_markup() without any explicit tooltip object. + * + * When you need a tooltip with a little more fancy contents, like adding an + * image, or you want the tooltip to have different contents per #GtkTreeView + * row or cell, you will have to do a little more work: + * + * + * + * Set the #GtkWidget:has-tooltip property to %TRUE, this will make GTK+ + * monitor the widget for motion and related events which are needed to + * determine when and where to show a tooltip. + * + * + * + * + * Connect to the #GtkWidget::query-tooltip signal. This signal will be + * emitted when a tooltip is supposed to be shown. One of the arguments passed + * to the signal handler is a GtkTooltip object. This is the object that we + * are about to display as a tooltip, and can be manipulated in your callback + * using functions like gtk_tooltip_set_icon(). There are functions for setting + * the tooltip's markup, setting an image from a stock icon, or even putting in + * a custom widget. + * + * + * + * + * Return %TRUE from your query-tooltip handler. This causes the tooltip to be + * show. If you return %FALSE, it will not be shown. + * + * + * + * + * In the probably rare case where you want to have even more control over the + * tooltip that is about to be shown, you can set your own #GtkWindow which + * will be used as tooltip window. This works as follows: + * + * + * + * Set #GtkWidget:has-tooltip and connect to #GtkWidget::query-tooltip as + * before. + * + * + * + * + * Use gtk_widget_set_tooltip_window() to set a #GtkWindow created by you as + * tooltip window. + * + * + * + * + * In the #GtkWidget::query-tooltip callback you can access your window using + * gtk_widget_get_tooltip_window() and manipulate as you wish. The semantics of + * the return value are exactly as before, return %TRUE to show the window, + * %FALSE to not show it. + * + * + * + */ + #undef DEBUG_TOOLTIP @@ -47,7 +121,6 @@ struct _GtkTooltip GObject parent_instance; GtkWidget *window; - GtkWidget *alignment; GtkWidget *box; GtkWidget *image; GtkWidget *label; @@ -71,6 +144,7 @@ struct _GtkTooltip guint browse_mode_enabled : 1; guint keyboard_mode_enabled : 1; guint tip_area_set : 1; + guint custom_was_reset : 1; }; struct _GtkTooltipClass @@ -78,19 +152,26 @@ struct _GtkTooltipClass GObjectClass parent_class; }; -#define GTK_TOOLTIP_VISIBLE(tooltip) ((tooltip)->current_window && GTK_WIDGET_VISIBLE ((tooltip)->current_window)) +#define GTK_TOOLTIP_VISIBLE(tooltip) ((tooltip)->current_window && gtk_widget_get_visible (GTK_WIDGET((tooltip)->current_window))) static void gtk_tooltip_class_init (GtkTooltipClass *klass); static void gtk_tooltip_init (GtkTooltip *tooltip); -static void gtk_tooltip_finalize (GObject *object); - -static gboolean gtk_tooltip_paint_window (GtkTooltip *tooltip); +static void gtk_tooltip_dispose (GObject *object); + +static gboolean gtk_tooltip_paint_window (GtkTooltip *tooltip, + cairo_t *cr); +static void gtk_tooltip_realize_window (GtkTooltip *tooltip, + GtkWidget *widget); +static void gtk_tooltip_composited_changed (GtkTooltip *tooltip, + GtkWidget *widget); static void gtk_tooltip_window_hide (GtkWidget *widget, gpointer user_data); static void gtk_tooltip_display_closed (GdkDisplay *display, gboolean was_error, GtkTooltip *tooltip); +static void gtk_tooltip_set_last_window (GtkTooltip *tooltip, + GdkWindow *window); G_DEFINE_TYPE (GtkTooltip, gtk_tooltip, G_TYPE_OBJECT); @@ -102,12 +183,20 @@ gtk_tooltip_class_init (GtkTooltipClass *klass) object_class = G_OBJECT_CLASS (klass); - object_class->finalize = gtk_tooltip_finalize; + object_class->dispose = gtk_tooltip_dispose; } static void gtk_tooltip_init (GtkTooltip *tooltip) { + GtkStyleContext *context; + GtkWidget *window; + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; + GdkScreen *screen; + GdkVisual *visual; + tooltip->timeout_id = 0; tooltip->browse_mode_timeout_id = 0; @@ -122,45 +211,55 @@ gtk_tooltip_init (GtkTooltip *tooltip) tooltip->last_window = NULL; - tooltip->window = g_object_ref (gtk_window_new (GTK_WINDOW_POPUP)); - gtk_window_set_type_hint (GTK_WINDOW (tooltip->window), - GDK_WINDOW_TYPE_HINT_TOOLTIP); - gtk_widget_set_app_paintable (tooltip->window, TRUE); - gtk_window_set_resizable (GTK_WINDOW (tooltip->window), FALSE); - gtk_widget_set_name (tooltip->window, "gtk-tooltip"); - g_signal_connect (tooltip->window, "hide", - G_CALLBACK (gtk_tooltip_window_hide), tooltip); - - tooltip->alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); - gtk_alignment_set_padding (GTK_ALIGNMENT (tooltip->alignment), - tooltip->window->style->ythickness, - tooltip->window->style->ythickness, - tooltip->window->style->xthickness, - tooltip->window->style->xthickness); - gtk_container_add (GTK_CONTAINER (tooltip->window), tooltip->alignment); - gtk_widget_show (tooltip->alignment); - - g_signal_connect_swapped (tooltip->window, "expose_event", - G_CALLBACK (gtk_tooltip_paint_window), tooltip); - - tooltip->box = gtk_hbox_new (FALSE, tooltip->window->style->xthickness); - gtk_container_add (GTK_CONTAINER (tooltip->alignment), tooltip->box); - gtk_widget_show (tooltip->box); - - tooltip->image = gtk_image_new (); - gtk_box_pack_start (GTK_BOX (tooltip->box), tooltip->image, - FALSE, FALSE, 0); - - tooltip->label = gtk_label_new (""); - gtk_label_set_line_wrap (GTK_LABEL (tooltip->label), TRUE); - gtk_box_pack_start (GTK_BOX (tooltip->box), tooltip->label, - FALSE, FALSE, 0); - + window = gtk_window_new (GTK_WINDOW_POPUP); + screen = gtk_widget_get_screen (window); + visual = gdk_screen_get_rgba_visual (screen); + + if (visual != NULL) + gtk_widget_set_visual (window, visual); + + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_TOOLTIP); + gtk_widget_set_app_paintable (window, TRUE); + gtk_window_set_resizable (GTK_WINDOW (window), FALSE); + gtk_widget_set_name (window, "gtk-tooltip"); + g_signal_connect (window, "hide", + G_CALLBACK (gtk_tooltip_window_hide), tooltip); + + context = gtk_widget_get_style_context (window); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_TOOLTIP); + + g_signal_connect_swapped (window, "draw", + G_CALLBACK (gtk_tooltip_paint_window), tooltip); + g_signal_connect_swapped (window, "realize", + G_CALLBACK (gtk_tooltip_realize_window), tooltip); + g_signal_connect_swapped (window, "composited-changed", + G_CALLBACK (gtk_tooltip_composited_changed), tooltip); + + /* FIXME: don't hardcode the padding */ + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_margin_left (box, 6); + gtk_widget_set_margin_right (box, 6); + gtk_widget_set_margin_top (box, 6); + gtk_widget_set_margin_bottom (box, 6); + gtk_container_add (GTK_CONTAINER (window), box); + gtk_widget_show (box); + + image = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); + + label = gtk_label_new (""); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + + tooltip->window = window; + tooltip->box = box; + tooltip->image = image; + tooltip->label = label; tooltip->custom_widget = NULL; } static void -gtk_tooltip_finalize (GObject *object) +gtk_tooltip_dispose (GObject *object) { GtkTooltip *tooltip = GTK_TOOLTIP (object); @@ -176,6 +275,9 @@ gtk_tooltip_finalize (GObject *object) tooltip->browse_mode_timeout_id = 0; } + gtk_tooltip_set_custom (tooltip, NULL); + gtk_tooltip_set_last_window (tooltip, NULL); + if (tooltip->window) { GdkDisplay *display; @@ -185,9 +287,10 @@ gtk_tooltip_finalize (GObject *object) gtk_tooltip_display_closed, tooltip); gtk_widget_destroy (tooltip->window); + tooltip->window = NULL; } - G_OBJECT_CLASS (gtk_tooltip_parent_class)->finalize (object); + G_OBJECT_CLASS (gtk_tooltip_parent_class)->dispose (object); } /* public API */ @@ -195,7 +298,7 @@ gtk_tooltip_finalize (GObject *object) /** * gtk_tooltip_set_markup: * @tooltip: a #GtkTooltip - * @markup: a markup string (see Pango markup format) or %NULL + * @markup: (allow-none): a markup string (see Pango markup format) or %NULL * * Sets the text of the tooltip to be @markup, which is marked up * with the image); } +/** + * gtk_tooltip_set_icon_from_icon_name: + * @tooltip: a #GtkTooltip + * @icon_name: (allow-none): an icon name, or %NULL + * @size: (type int): a stock icon size + * + * Sets the icon of the tooltip (which is in front of the text) to be + * the icon indicated by @icon_name with the size indicated + * by @size. If @icon_name is %NULL, the image will be hidden. + * + * Since: 2.14 + */ +void +gtk_tooltip_set_icon_from_icon_name (GtkTooltip *tooltip, + const gchar *icon_name, + GtkIconSize size) +{ + g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); + + gtk_image_set_from_icon_name (GTK_IMAGE (tooltip->image), icon_name, size); + + if (icon_name) + gtk_widget_show (tooltip->image); + else + gtk_widget_hide (tooltip->image); +} + +/** + * gtk_tooltip_set_icon_from_gicon: + * @tooltip: a #GtkTooltip + * @gicon: (allow-none): a #GIcon representing the icon, or %NULL + * @size: (type int): a stock icon size + * + * Sets the icon of the tooltip (which is in front of the text) + * to be the icon indicated by @gicon with the size indicated + * by @size. If @gicon is %NULL, the image will be hidden. + * + * Since: 2.20 + */ +void +gtk_tooltip_set_icon_from_gicon (GtkTooltip *tooltip, + GIcon *gicon, + GtkIconSize size) +{ + g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); + + gtk_image_set_from_gicon (GTK_IMAGE (tooltip->image), gicon, size); + + if (gicon) + gtk_widget_show (tooltip->image); + else + gtk_widget_hide (tooltip->image); +} + /** * gtk_tooltip_set_custom: * @tooltip: a #GtkTooltip - * @custom_widget: a #GtkWidget + * @custom_widget: (allow-none): a #GtkWidget, or %NULL to unset the old custom widget. * - * Replaces the widget packed into the tooltip with @custom_widget. + * Replaces the widget packed into the tooltip with + * @custom_widget. @custom_widget does not get destroyed when the tooltip goes + * away. * By default a box with a #GtkImage and #GtkLabel is embedded in * the tooltip, which can be configured using gtk_tooltip_set_markup() * and gtk_tooltip_set_icon(). + * * Since: 2.12 */ @@ -315,6 +475,15 @@ gtk_tooltip_set_custom (GtkTooltip *tooltip, if (custom_widget) g_return_if_fail (GTK_IS_WIDGET (custom_widget)); + /* The custom widget has been updated from the query-tooltip + * callback, so we do not want to reset the custom widget later on. + */ + tooltip->custom_was_reset = TRUE; + + /* No need to do anything if the custom widget stays the same */ + if (tooltip->custom_widget == custom_widget) + return; + if (tooltip->custom_widget) { GtkWidget *custom = tooltip->custom_widget; @@ -353,8 +522,8 @@ gtk_tooltip_set_custom (GtkTooltip *tooltip, * Since: 2.12 */ void -gtk_tooltip_set_tip_area (GtkTooltip *tooltip, - GdkRectangle *rect) +gtk_tooltip_set_tip_area (GtkTooltip *tooltip, + const GdkRectangle *rect) { g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); @@ -384,9 +553,11 @@ gtk_tooltip_trigger_tooltip_query (GdkDisplay *display) gint x, y; GdkWindow *window; GdkEvent event; + GdkDevice *device; /* Trigger logic as if the mouse moved */ - window = gdk_display_get_window_at_pointer (display, &x, &y); + device = gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (display)); + window = gdk_device_get_window_at_position (device, &x, &y); if (!window) return; @@ -396,6 +567,10 @@ gtk_tooltip_trigger_tooltip_query (GdkDisplay *display) event.motion.y = y; event.motion.is_hint = FALSE; + gdk_window_get_root_coords (window, x, y, &x, &y); + event.motion.x_root = x; + event.motion.y_root = y; + _gtk_tooltip_handle_event (&event); } @@ -406,23 +581,92 @@ gtk_tooltip_reset (GtkTooltip *tooltip) { gtk_tooltip_set_markup (tooltip, NULL); gtk_tooltip_set_icon (tooltip, NULL); - gtk_tooltip_set_custom (tooltip, NULL); gtk_tooltip_set_tip_area (tooltip, NULL); + + /* See if the custom widget is again set from the query-tooltip + * callback. + */ + tooltip->custom_was_reset = FALSE; +} + +static void +paint_background_and_frame (GtkTooltip *tooltip, + cairo_t *cr) +{ + GtkStyleContext *context; + gint width, height; + + width = gtk_widget_get_allocated_width (tooltip->window); + height = gtk_widget_get_allocated_height (tooltip->window); + context = gtk_widget_get_style_context (tooltip->window); + + gtk_render_background (context, cr, + 0, 0, width, height); + gtk_render_frame (context, cr, + 0, 0, width, height); +} + +static void +maybe_update_shape (GtkTooltip *tooltip) +{ + cairo_t *cr; + cairo_surface_t *surface; + cairo_region_t *region; + + /* fallback to XShape only for non-composited clients */ + if (gtk_widget_is_composited (tooltip->window)) + { + gtk_widget_shape_combine_region (tooltip->window, NULL); + return; + } + + surface = gdk_window_create_similar_surface (gtk_widget_get_window (tooltip->window), + CAIRO_CONTENT_COLOR_ALPHA, + gtk_widget_get_allocated_width (tooltip->window), + gtk_widget_get_allocated_height (tooltip->window)); + + cr = cairo_create (surface); + paint_background_and_frame (tooltip, cr); + cairo_destroy (cr); + + region = gdk_cairo_region_create_from_surface (surface); + gtk_widget_shape_combine_region (tooltip->window, region); + + cairo_surface_destroy (surface); + cairo_region_destroy (region); +} + +static void +gtk_tooltip_composited_changed (GtkTooltip *tooltip, + GtkWidget *widget) +{ + if (gtk_widget_get_realized (tooltip->window)) + maybe_update_shape (tooltip); +} + +static void +gtk_tooltip_realize_window (GtkTooltip *tooltip, + GtkWidget *widget) +{ + maybe_update_shape (tooltip); } static gboolean -gtk_tooltip_paint_window (GtkTooltip *tooltip) +gtk_tooltip_paint_window (GtkTooltip *tooltip, + cairo_t *cr) { - gtk_paint_flat_box (tooltip->window->style, - tooltip->window->window, - GTK_STATE_NORMAL, - GTK_SHADOW_OUT, - NULL, - tooltip->window, - "tooltip", - 0, 0, - tooltip->window->allocation.width, - tooltip->window->allocation.height); + if (gtk_widget_is_composited (tooltip->window)) + { + /* clear any background */ + cairo_save (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + cairo_restore (cr); + } + + maybe_update_shape (tooltip); + paint_background_and_frame (tooltip, cr); return FALSE; } @@ -451,13 +695,19 @@ static void child_location_foreach (GtkWidget *child, gpointer data) { + GtkAllocation child_allocation; gint x, y; struct ChildLocation *child_loc = data; /* Ignore invisible widgets */ - if (!GTK_WIDGET_DRAWABLE (child)) + if (!gtk_widget_is_drawable (child)) return; + gtk_widget_get_allocation (child, &child_allocation); + + x = 0; + y = 0; + /* (child_loc->x, child_loc->y) are relative to * child_loc->container's allocation. */ @@ -470,17 +720,17 @@ child_location_foreach (GtkWidget *child, #ifdef DEBUG_TOOLTIP g_print ("candidate: %s alloc=[(%d,%d) %dx%d] (%d, %d)->(%d, %d)\n", gtk_widget_get_name (child), - child->allocation.x, - child->allocation.y, - child->allocation.width, - child->allocation.height, + child_allocation.x, + child_allocation.y, + child_allocation.width, + child_allocation.height, child_loc->x, child_loc->y, x, y); #endif /* DEBUG_TOOLTIP */ /* (x, y) relative to child's allocation. */ - if (x >= 0 && x < child->allocation.width - && y >= 0 && y < child->allocation.height) + if (x >= 0 && x < child_allocation.width + && y >= 0 && y < child_allocation.height) { if (GTK_IS_CONTAINER (child)) { @@ -517,22 +767,28 @@ window_to_alloc (GtkWidget *dest_widget, gint *dest_x, gint *dest_y) { + GtkAllocation allocation; + + gtk_widget_get_allocation (dest_widget, &allocation); + /* Translate from window relative to allocation relative */ - if (!GTK_WIDGET_NO_WINDOW (dest_widget) && dest_widget->parent) + if (gtk_widget_get_has_window (dest_widget) && + gtk_widget_get_parent (dest_widget)) { gint wx, wy; - gdk_window_get_position (dest_widget->window, &wx, &wy); + gdk_window_get_position (gtk_widget_get_window (dest_widget), + &wx, &wy); /* Offset coordinates if widget->window is smaller than * widget->allocation. */ - src_x += wx - dest_widget->allocation.x; - src_y += wy - dest_widget->allocation.y; + src_x += wx - allocation.x; + src_y += wy - allocation.y; } else { - src_x -= dest_widget->allocation.x; - src_y -= dest_widget->allocation.y; + src_x -= allocation.x; + src_y -= allocation.y; } if (dest_x) @@ -544,14 +800,18 @@ window_to_alloc (GtkWidget *dest_widget, /* Translates coordinates from window relative (x, y) to * allocation relative (x, y) of the returned widget. */ -static GtkWidget * -find_widget_under_pointer (GdkWindow *window, - gint *x, - gint *y) +GtkWidget * +_gtk_widget_find_at_coords (GdkWindow *window, + gint window_x, + gint window_y, + gint *widget_x, + gint *widget_y) { GtkWidget *event_widget; struct ChildLocation child_loc = { NULL, NULL, 0, 0 }; + g_return_val_if_fail (GDK_IS_WINDOW (window), NULL); + gdk_window_get_user_data (window, (void **)&event_widget); if (!event_widget) @@ -560,26 +820,28 @@ find_widget_under_pointer (GdkWindow *window, #ifdef DEBUG_TOOLTIP g_print ("event window %p (belonging to %p (%s)) (%d, %d)\n", window, event_widget, gtk_widget_get_name (event_widget), - *x, *y); + window_x, window_y); #endif /* Coordinates are relative to event window */ - child_loc.x = *x; - child_loc.y = *y; + child_loc.x = window_x; + child_loc.y = window_y; /* We go down the window hierarchy to the widget->window, * coordinates stay relative to the current window. * We end up with window == widget->window, coordinates relative to that. */ - while (window && window != event_widget->window) + while (window && window != gtk_widget_get_window (event_widget)) { - gint px, py; + gdouble px, py; - gdk_window_get_position (window, &px, &py); - child_loc.x += px; - child_loc.y += py; + gdk_window_coords_to_parent (window, + child_loc.x, child_loc.y, + &px, &py); + child_loc.x = px; + child_loc.y = py; - window = gdk_window_get_parent (window); + window = gdk_window_get_effective_parent (window); } /* Failing to find widget->window can happen for e.g. a detached handle box; @@ -620,14 +882,13 @@ find_widget_under_pointer (GdkWindow *window, gtk_widget_translate_coordinates (container, event_widget, child_loc.x, child_loc.y, &child_loc.x, &child_loc.y); - } /* We return (x, y) relative to the allocation of event_widget. */ - if (x) - *x = child_loc.x; - if (y) - *y = child_loc.y; + if (widget_x) + *widget_x = child_loc.x; + if (widget_y) + *widget_y = child_loc.y; return event_widget; } @@ -640,23 +901,23 @@ find_topmost_widget_coords_from_event (GdkEvent *event, gint *x, gint *y) { + GtkAllocation allocation; gint tx, ty; gdouble dx, dy; GtkWidget *tmp; gdk_event_get_coords (event, &dx, &dy); - tx = dx; - ty = dy; /* Returns coordinates relative to tmp's allocation. */ - tmp = find_widget_under_pointer (event->any.window, &tx, &ty); + tmp = _gtk_widget_find_at_coords (event->any.window, dx, dy, &tx, &ty); if (!tmp) return NULL; /* Make sure the pointer can actually be on the widget returned. */ - if (tx < 0 || tx >= tmp->allocation.width || - ty < 0 || ty >= tmp->allocation.height) + gtk_widget_get_allocation (tmp, &allocation); + if (tx < 0 || tx >= allocation.width || + ty < 0 || ty >= allocation.height) return NULL; if (x) @@ -689,7 +950,25 @@ gtk_tooltip_display_closed (GdkDisplay *display, gboolean was_error, GtkTooltip *tooltip) { - g_object_set (display, "gdk-display-current-tooltip", NULL); + g_object_set_data (G_OBJECT (display), "gdk-display-current-tooltip", NULL); +} + +static void +gtk_tooltip_set_last_window (GtkTooltip *tooltip, + GdkWindow *window) +{ + if (tooltip->last_window == window) + return; + + if (tooltip->last_window) + g_object_remove_weak_pointer (G_OBJECT (tooltip->last_window), + (gpointer *) &tooltip->last_window); + + tooltip->last_window = window; + + if (window) + g_object_add_weak_pointer (G_OBJECT (tooltip->last_window), + (gpointer *) &tooltip->last_window); } static gboolean @@ -719,7 +998,7 @@ gtk_tooltip_run_requery (GtkWidget **widget, if (!return_value) { - GtkWidget *parent = (*widget)->parent; + GtkWidget *parent = gtk_widget_get_parent (*widget); if (parent) gtk_widget_translate_coordinates (*widget, parent, *x, *y, x, y); @@ -731,82 +1010,215 @@ gtk_tooltip_run_requery (GtkWidget **widget, } while (*widget); + /* If the custom widget was not reset in the query-tooltip + * callback, we clear it here. + */ + if (!tooltip->custom_was_reset) + gtk_tooltip_set_custom (tooltip, NULL); + return return_value; } +static void +get_bounding_box (GtkWidget *widget, + GdkRectangle *bounds) +{ + GtkAllocation allocation; + GdkWindow *window; + gint x, y; + gint w, h; + gint x1, y1; + gint x2, y2; + gint x3, y3; + gint x4, y4; + + window = gtk_widget_get_parent_window (widget); + if (window == NULL) + window = gtk_widget_get_window (widget); + + gtk_widget_get_allocation (widget, &allocation); + x = allocation.x; + y = allocation.y; + w = allocation.width; + h = allocation.height; + + gdk_window_get_root_coords (window, x, y, &x1, &y1); + gdk_window_get_root_coords (window, x + w, y, &x2, &y2); + gdk_window_get_root_coords (window, x, y + h, &x3, &y3); + gdk_window_get_root_coords (window, x + w, y + h, &x4, &y4); + +#define MIN4(a,b,c,d) MIN(MIN(a,b),MIN(c,d)) +#define MAX4(a,b,c,d) MAX(MAX(a,b),MAX(c,d)) + + bounds->x = floor (MIN4 (x1, x2, x3, x4)); + bounds->y = floor (MIN4 (y1, y2, y3, y4)); + bounds->width = ceil (MAX4 (x1, x2, x3, x4)) - bounds->x; + bounds->height = ceil (MAX4 (y1, y2, y3, y4)) - bounds->y; +} + static void gtk_tooltip_position (GtkTooltip *tooltip, GdkDisplay *display, GtkWidget *new_tooltip_widget) { - gint x, y; + gint x, y, width, height; GdkScreen *screen; + gint monitor_num; + GdkRectangle monitor; + guint cursor_size; + GdkRectangle bounds; + +#define MAX_DISTANCE 32 + + gtk_widget_realize (GTK_WIDGET (tooltip->current_window)); tooltip->tooltip_widget = new_tooltip_widget; + screen = gtk_widget_get_screen (new_tooltip_widget); + + width = gtk_widget_get_allocated_width (GTK_WIDGET (tooltip->current_window)); + height = gtk_widget_get_allocated_height (GTK_WIDGET (tooltip->current_window)); + + monitor_num = gdk_screen_get_monitor_at_point (screen, + tooltip->last_x, + tooltip->last_y); + gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor); + + get_bounding_box (new_tooltip_widget, &bounds); + /* Position the tooltip */ - /* FIXME: should we swap this when RTL is enabled? */ - if (tooltip->keyboard_mode_enabled) + + cursor_size = gdk_display_get_default_cursor_size (display); + + /* Try below */ + x = bounds.x + bounds.width / 2 - width / 2; + y = bounds.y + bounds.height + 4; + + if (y + height <= monitor.y + monitor.height) { - gdk_window_get_origin (new_tooltip_widget->window, &x, &y); - if (GTK_WIDGET_NO_WINDOW (new_tooltip_widget)) + if (tooltip->keyboard_mode_enabled) + goto found; + + if (y <= tooltip->last_y + cursor_size + MAX_DISTANCE) { - x += new_tooltip_widget->allocation.x; - y += new_tooltip_widget->allocation.y; - } + if (tooltip->last_x + cursor_size + MAX_DISTANCE < x) + x = tooltip->last_x + cursor_size + MAX_DISTANCE; + else if (x + width < tooltip->last_x - MAX_DISTANCE) + x = tooltip->last_x - MAX_DISTANCE - width; - /* For keyboard mode we position the tooltip below the widget, - * right of the center of the widget. - */ - x += new_tooltip_widget->allocation.width / 2; - y += new_tooltip_widget->allocation.height + 4; + goto found; + } + } + + /* Try above */ + x = bounds.x + bounds.width / 2 - width / 2; + y = bounds.y - height - 4; + + if (y >= monitor.y) + { + if (tooltip->keyboard_mode_enabled) + goto found; + + if (y + height >= tooltip->last_y - MAX_DISTANCE) + { + if (tooltip->last_x + cursor_size + MAX_DISTANCE < x) + x = tooltip->last_x + cursor_size + MAX_DISTANCE; + else if (x + width < tooltip->last_x - MAX_DISTANCE) + x = tooltip->last_x - MAX_DISTANCE - width; + + goto found; + } } - else + + /* Try right FIXME: flip on rtl ? */ + x = bounds.x + bounds.width + 4; + y = bounds.y + bounds.height / 2 - height / 2; + + if (x + width <= monitor.x + monitor.width) { - guint cursor_size; + if (tooltip->keyboard_mode_enabled) + goto found; - x = tooltip->last_x; - y = tooltip->last_y; + if (x <= tooltip->last_x + cursor_size + MAX_DISTANCE) + { + if (tooltip->last_y + cursor_size + MAX_DISTANCE < y) + y = tooltip->last_y + cursor_size + MAX_DISTANCE; + else if (y + height < tooltip->last_y - MAX_DISTANCE) + y = tooltip->last_y - MAX_DISTANCE - height; - /* For mouse mode, we position the tooltip right of the cursor, - * a little below the cursor's center. - */ - cursor_size = gdk_display_get_default_cursor_size (display); - x += cursor_size / 2; - y += cursor_size / 2; + goto found; + } } - screen = gtk_widget_get_screen (new_tooltip_widget); + /* Try left FIXME: flip on rtl ? */ + x = bounds.x - width - 4; + y = bounds.y + bounds.height / 2 - height / 2; - /* Show it */ - if (tooltip->current_window) + if (x >= monitor.x) { - gint monitor_num; - GdkRectangle monitor; - GtkRequisition requisition; + if (tooltip->keyboard_mode_enabled) + goto found; - gtk_widget_size_request (GTK_WIDGET (tooltip->current_window), - &requisition); + if (x + width >= tooltip->last_x - MAX_DISTANCE) + { + if (tooltip->last_y + cursor_size + MAX_DISTANCE < y) + y = tooltip->last_y + cursor_size + MAX_DISTANCE; + else if (y + height < tooltip->last_y - MAX_DISTANCE) + y = tooltip->last_y - MAX_DISTANCE - height; - monitor_num = gdk_screen_get_monitor_at_point (screen, x, y); - gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + goto found; + } + } - if (x + requisition.width > monitor.x + monitor.width) - x -= x - (monitor.x + monitor.width) + requisition.width; + /* Fallback */ + if (tooltip->keyboard_mode_enabled) + { + x = bounds.x + bounds.width / 2 - width / 2; + y = bounds.y + bounds.height + 4; + } + else + { + /* At cursor */ + x = tooltip->last_x + cursor_size * 3 / 4; + y = tooltip->last_y + cursor_size * 3 / 4; + } + +found: + /* Show it */ + if (tooltip->current_window) + { + if (x + width > monitor.x + monitor.width) + x -= x - (monitor.x + monitor.width) + width; else if (x < monitor.x) x = monitor.x; - if (y + requisition.height > monitor.y + monitor.height) - y -= y - (monitor.y + monitor.height) + requisition.height; - + if (y + height > monitor.y + monitor.height) + y -= y - (monitor.y + monitor.height) + height; + else if (y < monitor.y) + y = monitor.y; + if (!tooltip->keyboard_mode_enabled) { /* don't pop up under the pointer */ - if (x <= tooltip->last_x && tooltip->last_x < x + requisition.width && - y <= tooltip->last_y && tooltip->last_y < y + requisition.height) - y = tooltip->last_y - requisition.height - 2; + if (x <= tooltip->last_x && tooltip->last_x < x + width && + y <= tooltip->last_y && tooltip->last_y < y + height) + y = tooltip->last_y - height - 2; } - + +#ifdef GDK_WINDOWING_WAYLAND + /* set the transient parent on the tooltip when running with the Wayland + * backend to allow correct positioning of the tooltip windows */ + if (GDK_IS_WAYLAND_DISPLAY (display)) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (tooltip->tooltip_widget); + if (GTK_IS_WINDOW (toplevel)) + gtk_window_set_transient_for (GTK_WINDOW (tooltip->current_window), + GTK_WINDOW (toplevel)); + } +#endif + gtk_window_move (GTK_WINDOW (tooltip->current_window), x, y); gtk_widget_show (GTK_WIDGET (tooltip->current_window)); } @@ -820,31 +1232,37 @@ gtk_tooltip_show_tooltip (GdkDisplay *display) GdkWindow *window; GtkWidget *tooltip_widget; - GtkWidget *pointer_widget; GtkTooltip *tooltip; gboolean has_tooltip; gboolean return_value = FALSE; tooltip = g_object_get_data (G_OBJECT (display), - "gdk-display-current-tooltip"); + "gdk-display-current-tooltip"); if (tooltip->keyboard_mode_enabled) { - pointer_widget = tooltip_widget = tooltip->keyboard_widget; + x = y = -1; + tooltip_widget = tooltip->keyboard_widget; } else { + GdkDevice *device; + gint tx, ty; + window = tooltip->last_window; if (!GDK_IS_WINDOW (window)) - return; + return; - gdk_window_get_origin (window, &x, &y); - x = tooltip->last_x - x; - y = tooltip->last_y - y; + device = gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (display)); - pointer_widget = tooltip_widget = find_widget_under_pointer (window, - &x, &y); + gdk_window_get_device_position (window, device, &x, &y, NULL); + + gdk_window_get_root_coords (window, x, y, &tx, &ty); + tooltip->last_x = tx; + tooltip->last_y = ty; + + tooltip_widget = _gtk_widget_find_at_coords (window, x, y, &x, &y); } if (!tooltip_widget) @@ -852,8 +1270,6 @@ gtk_tooltip_show_tooltip (GdkDisplay *display) g_object_get (tooltip_widget, "has-tooltip", &has_tooltip, NULL); - g_assert (tooltip != NULL); - return_value = gtk_tooltip_run_requery (&tooltip_widget, tooltip, &x, &y); if (!return_value) return; @@ -861,9 +1277,9 @@ gtk_tooltip_show_tooltip (GdkDisplay *display) if (!tooltip->current_window) { if (gtk_widget_get_tooltip_window (tooltip_widget)) - tooltip->current_window = gtk_widget_get_tooltip_window (tooltip_widget); + tooltip->current_window = gtk_widget_get_tooltip_window (tooltip_widget); else - tooltip->current_window = GTK_WINDOW (GTK_TOOLTIP (tooltip)->window); + tooltip->current_window = GTK_WINDOW (GTK_TOOLTIP (tooltip)->window); } screen = gtk_widget_get_screen (tooltip_widget); @@ -872,13 +1288,13 @@ gtk_tooltip_show_tooltip (GdkDisplay *display) if (screen != gtk_widget_get_screen (tooltip->window)) { g_signal_handlers_disconnect_by_func (display, - gtk_tooltip_display_closed, - tooltip); + gtk_tooltip_display_closed, + tooltip); gtk_window_set_screen (GTK_WINDOW (tooltip->window), screen); g_signal_connect (display, "closed", - G_CALLBACK (gtk_tooltip_display_closed), tooltip); + G_CALLBACK (gtk_tooltip_display_closed), tooltip); } gtk_tooltip_position (tooltip, display, tooltip_widget); @@ -954,12 +1370,18 @@ tooltip_popup_timeout (gpointer data) GdkDisplay *display; GtkTooltip *tooltip; - display = GDK_DISPLAY_OBJECT (data); + display = GDK_DISPLAY (data); + tooltip = g_object_get_data (G_OBJECT (display), + "gdk-display-current-tooltip"); + + /* This usually does not happen. However, it does occur in language + * bindings were reference counting of objects behaves differently. + */ + if (!tooltip) + return FALSE; gtk_tooltip_show_tooltip (display); - tooltip = g_object_get_data (G_OBJECT (display), - "gdk-display-current-tooltip"); tooltip->timeout_id = 0; return FALSE; @@ -975,7 +1397,7 @@ gtk_tooltip_start_delay (GdkDisplay *display) tooltip = g_object_get_data (G_OBJECT (display), "gdk-display-current-tooltip"); - if (tooltip && GTK_TOOLTIP_VISIBLE (tooltip)) + if (!tooltip || GTK_TOOLTIP_VISIBLE (tooltip)) return; if (tooltip->timeout_id) @@ -1001,6 +1423,7 @@ _gtk_tooltip_focus_in (GtkWidget *widget) gboolean return_value = FALSE; GdkDisplay *display; GtkTooltip *tooltip; + GdkDevice *device; /* Get current tooltip for this display */ display = gtk_widget_get_display (widget); @@ -1011,12 +1434,24 @@ _gtk_tooltip_focus_in (GtkWidget *widget) if (!tooltip || !tooltip->keyboard_mode_enabled) return; + device = gtk_get_current_event_device (); + + if (device && gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) + device = gdk_device_get_associated_device (device); + + /* This function should be called by either a focus in event, + * or a key binding. In either case there should be a device. + */ + if (!device) + return; + if (tooltip->keyboard_widget) g_object_unref (tooltip->keyboard_widget); tooltip->keyboard_widget = g_object_ref (widget); - gdk_window_get_pointer (widget->window, &x, &y, NULL); + gdk_window_get_device_position (gtk_widget_get_window (widget), + device, &x, &y, NULL); return_value = gtk_tooltip_run_requery (&widget, tooltip, &x, &y); if (!return_value) @@ -1075,6 +1510,9 @@ _gtk_tooltip_toggle_keyboard_mode (GtkWidget *widget) g_object_set_data_full (G_OBJECT (display), "gdk-display-current-tooltip", tooltip, g_object_unref); + g_signal_connect (display, "closed", + G_CALLBACK (gtk_tooltip_display_closed), + tooltip); } tooltip->keyboard_mode_enabled ^= 1; @@ -1113,42 +1551,61 @@ _gtk_tooltip_hide (GtkWidget *widget) toplevel = gtk_widget_get_toplevel (widget); if (widget == tooltip->tooltip_widget - || toplevel->window == tooltip->toplevel_window) + || gtk_widget_get_window (toplevel) == tooltip->toplevel_window) gtk_tooltip_hide_tooltip (tooltip); } +static gboolean +tooltips_enabled (GdkEvent *event) +{ + GdkDevice *source_device; + GdkInputSource source; + GdkWindow *window; + gboolean enabled; + GdkScreen *screen; + GtkSettings *settings; + + window = event->any.window; + source_device = gdk_event_get_source_device (event); + + if (!source_device) + return FALSE; + + source = gdk_device_get_source (source_device); + screen = gdk_window_get_screen (window); + settings = gtk_settings_get_for_screen (screen); + + g_object_get (settings, + "gtk-enable-tooltips", &enabled, + NULL); + + if (enabled && source != GDK_SOURCE_TOUCHSCREEN) + return TRUE; + + return FALSE; +} + void _gtk_tooltip_handle_event (GdkEvent *event) { gint x, y; gboolean return_value = FALSE; - gboolean touchscreen; GtkWidget *has_tooltip_widget = NULL; - GdkScreen *screen; GdkDisplay *display; GtkTooltip *current_tooltip; - GtkSettings *settings; - /* Disable tooltips in touchscreen mode */ - screen = gdk_drawable_get_screen (event->any.window); - settings = gtk_settings_get_for_screen (screen); - g_object_get (settings, "gtk-touchscreen-mode", &touchscreen, NULL); - - if (touchscreen) + if (!tooltips_enabled (event)) return; /* Returns coordinates relative to has_tooltip_widget's allocation. */ has_tooltip_widget = find_topmost_widget_coords_from_event (event, &x, &y); - display = gdk_drawable_get_display (event->any.window); + display = gdk_window_get_display (event->any.window); current_tooltip = g_object_get_data (G_OBJECT (display), "gdk-display-current-tooltip"); if (current_tooltip) { - current_tooltip->last_window = event->any.window; - gdk_event_get_root_coords (event, - ¤t_tooltip->last_x, - ¤t_tooltip->last_y); + gtk_tooltip_set_last_window (current_tooltip, event->any.window); } if (current_tooltip && current_tooltip->keyboard_mode_enabled) @@ -1200,13 +1657,13 @@ _gtk_tooltip_handle_event (GdkEvent *event) case GDK_KEY_PRESS: case GDK_DRAG_ENTER: case GDK_GRAB_BROKEN: + case GDK_SCROLL: gtk_tooltip_hide_tooltip (current_tooltip); break; case GDK_MOTION_NOTIFY: case GDK_ENTER_NOTIFY: case GDK_LEAVE_NOTIFY: - case GDK_SCROLL: if (current_tooltip) { gboolean tip_area_set; @@ -1223,6 +1680,9 @@ _gtk_tooltip_handle_event (GdkEvent *event) /* Requested to be hidden? */ hide_tooltip = !return_value; + /* Leave notify should override the query function */ + hide_tooltip = (event->type == GDK_LEAVE_NOTIFY); + /* Is the pointer above another widget now? */ if (GTK_TOOLTIP_VISIBLE (current_tooltip)) hide_tooltip |= has_tooltip_widget != current_tooltip->tooltip_widget; @@ -1246,11 +1706,11 @@ _gtk_tooltip_handle_event (GdkEvent *event) g_object_set_data_full (G_OBJECT (display), "gdk-display-current-tooltip", current_tooltip, g_object_unref); + g_signal_connect (display, "closed", + G_CALLBACK (gtk_tooltip_display_closed), + current_tooltip); - current_tooltip->last_window = event->any.window; - gdk_event_get_root_coords (event, - ¤t_tooltip->last_x, - ¤t_tooltip->last_y); + gtk_tooltip_set_last_window (current_tooltip, event->any.window); gtk_tooltip_start_delay (display); } @@ -1260,7 +1720,3 @@ _gtk_tooltip_handle_event (GdkEvent *event) break; } } - - -#define __GTK_TOOLTIP_C__ -#include "gtkaliasdef.c"