forked from AuroraMiddleware/gtk
6df28420d7
In order to make tooltip positioning portable, make use of the move_to_rect API. Some semantical changes are made, as identical semantics cannot be implemented using the move-to-rect API. Primarily the implemented semantics are: Position the tooltip in the center pixels slightly below (defaults to 4 units below) the tooltipped widget. This is always the case for keyboard driven tooltips; the case where it tries to avoid the pointer cursor is not implemented. For pointer position triggered tooltips, implement the following additional semantics: Use the current cursor size to determine the padding used to enlarge the anchor rectangle. This is to try to avoid the cursor overlapping the tooltip. If the anchor rectangle is too tall (meaning if we'd be constrained and flip on the Y axis, it'd flip too far away from the originally intended position), rely only on the pointer position to position the tooltip. The approximate pointer cursor rectangle is used as a anchor rectangle. Ideally we should use the actual pointer cursor rectangle (image used as well as hotspot coordinate), but we don't have API to get that information. If the anchor rectangle isn't to tall, just make sure the tooltip isn't too far away from the pointer position on the X axis. Closes: #134 Closes: #432 Closes: #574 Closes: #579 Closes: #878
1168 lines
32 KiB
C
1168 lines
32 KiB
C
/* gtktooltip.c
|
||
*
|
||
* Copyright (C) 2006-2007 Imendio AB
|
||
* Contact: Kristian Rietveld <kris@imendio.com>
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Library 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
|
||
* 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, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include "gtktooltip.h"
|
||
#include "gtktooltipprivate.h"
|
||
|
||
#include <math.h>
|
||
#include <string.h>
|
||
|
||
#include "gtkintl.h"
|
||
#include "gtkwindow.h"
|
||
#include "gtkmain.h"
|
||
#include "gtksettings.h"
|
||
#include "gtksizerequest.h"
|
||
#include "gtkstylecontext.h"
|
||
#include "gtktooltipwindowprivate.h"
|
||
#include "gtkwindowprivate.h"
|
||
#include "gtkwidgetprivate.h"
|
||
#include "gtkaccessible.h"
|
||
|
||
#ifdef GDK_WINDOWING_WAYLAND
|
||
#include "wayland/gdkwayland.h"
|
||
#endif
|
||
#ifdef GDK_WINDOWING_MIR
|
||
#include "mir/gdkmir.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 named 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.
|
||
*/
|
||
|
||
|
||
#define HOVER_TIMEOUT 500
|
||
#define BROWSE_TIMEOUT 60
|
||
#define BROWSE_DISABLE_TIMEOUT 500
|
||
|
||
#define GTK_TOOLTIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TOOLTIP, GtkTooltipClass))
|
||
#define GTK_IS_TOOLTIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TOOLTIP))
|
||
#define GTK_TOOLTIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TOOLTIP, GtkTooltipClass))
|
||
|
||
typedef struct _GtkTooltipClass GtkTooltipClass;
|
||
|
||
struct _GtkTooltip
|
||
{
|
||
GObject parent_instance;
|
||
|
||
GtkWidget *window;
|
||
|
||
GtkWindow *current_window;
|
||
GtkWidget *keyboard_widget;
|
||
|
||
GtkWidget *tooltip_widget;
|
||
|
||
GdkSurface *last_surface;
|
||
|
||
guint timeout_id;
|
||
guint browse_mode_timeout_id;
|
||
|
||
GdkRectangle tip_area;
|
||
|
||
guint browse_mode_enabled : 1;
|
||
guint keyboard_mode_enabled : 1;
|
||
guint tip_area_set : 1;
|
||
guint custom_was_reset : 1;
|
||
};
|
||
|
||
struct _GtkTooltipClass
|
||
{
|
||
GObjectClass parent_class;
|
||
};
|
||
|
||
#define GTK_TOOLTIP_VISIBLE(tooltip) ((tooltip)->current_window && gtk_widget_get_visible (GTK_WIDGET((tooltip)->current_window)))
|
||
|
||
static void gtk_tooltip_dispose (GObject *object);
|
||
|
||
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_surface (GtkTooltip *tooltip,
|
||
GdkSurface *surface);
|
||
|
||
static void gtk_tooltip_handle_event_internal (GdkEventType event_type,
|
||
GdkSurface *surface,
|
||
gdouble dx,
|
||
gdouble dy);
|
||
|
||
static inline GQuark tooltip_quark (void)
|
||
{
|
||
static GQuark quark;
|
||
|
||
if G_UNLIKELY (quark == 0)
|
||
quark = g_quark_from_static_string ("gdk-display-current-tooltip");
|
||
return quark;
|
||
}
|
||
|
||
#define quark_current_tooltip tooltip_quark()
|
||
|
||
G_DEFINE_TYPE (GtkTooltip, gtk_tooltip, G_TYPE_OBJECT);
|
||
|
||
static void
|
||
gtk_tooltip_class_init (GtkTooltipClass *klass)
|
||
{
|
||
GObjectClass *object_class;
|
||
|
||
object_class = G_OBJECT_CLASS (klass);
|
||
|
||
object_class->dispose = gtk_tooltip_dispose;
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_init (GtkTooltip *tooltip)
|
||
{
|
||
tooltip->timeout_id = 0;
|
||
tooltip->browse_mode_timeout_id = 0;
|
||
|
||
tooltip->browse_mode_enabled = FALSE;
|
||
tooltip->keyboard_mode_enabled = FALSE;
|
||
|
||
tooltip->current_window = NULL;
|
||
tooltip->keyboard_widget = NULL;
|
||
|
||
tooltip->tooltip_widget = NULL;
|
||
|
||
tooltip->last_surface = NULL;
|
||
|
||
tooltip->window = gtk_tooltip_window_new ();
|
||
g_signal_connect (tooltip->window, "hide",
|
||
G_CALLBACK (gtk_tooltip_window_hide),
|
||
tooltip);
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_dispose (GObject *object)
|
||
{
|
||
GtkTooltip *tooltip = GTK_TOOLTIP (object);
|
||
|
||
if (tooltip->timeout_id)
|
||
{
|
||
g_source_remove (tooltip->timeout_id);
|
||
tooltip->timeout_id = 0;
|
||
}
|
||
|
||
if (tooltip->browse_mode_timeout_id)
|
||
{
|
||
g_source_remove (tooltip->browse_mode_timeout_id);
|
||
tooltip->browse_mode_timeout_id = 0;
|
||
}
|
||
|
||
gtk_tooltip_set_custom (tooltip, NULL);
|
||
gtk_tooltip_set_last_surface (tooltip, NULL);
|
||
|
||
if (tooltip->window)
|
||
{
|
||
GdkDisplay *display;
|
||
|
||
display = gtk_widget_get_display (tooltip->window);
|
||
g_signal_handlers_disconnect_by_func (display,
|
||
gtk_tooltip_display_closed,
|
||
tooltip);
|
||
gtk_widget_destroy (tooltip->window);
|
||
tooltip->window = NULL;
|
||
}
|
||
|
||
G_OBJECT_CLASS (gtk_tooltip_parent_class)->dispose (object);
|
||
}
|
||
|
||
/* public API */
|
||
|
||
/**
|
||
* gtk_tooltip_set_markup:
|
||
* @tooltip: a #GtkTooltip
|
||
* @markup: (allow-none): a markup string (see [Pango markup format][PangoMarkupFormat]) or %NULL
|
||
*
|
||
* Sets the text of the tooltip to be @markup, which is marked up
|
||
* with the [Pango text markup language][PangoMarkupFormat].
|
||
* If @markup is %NULL, the label will be hidden.
|
||
*/
|
||
void
|
||
gtk_tooltip_set_markup (GtkTooltip *tooltip,
|
||
const gchar *markup)
|
||
{
|
||
g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
|
||
|
||
gtk_tooltip_window_set_label_markup (GTK_TOOLTIP_WINDOW (tooltip->window), markup);
|
||
}
|
||
|
||
/**
|
||
* gtk_tooltip_set_text:
|
||
* @tooltip: a #GtkTooltip
|
||
* @text: (allow-none): a text string or %NULL
|
||
*
|
||
* Sets the text of the tooltip to be @text. If @text is %NULL, the label
|
||
* will be hidden. See also gtk_tooltip_set_markup().
|
||
*/
|
||
void
|
||
gtk_tooltip_set_text (GtkTooltip *tooltip,
|
||
const gchar *text)
|
||
{
|
||
g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
|
||
|
||
gtk_tooltip_window_set_label_text (GTK_TOOLTIP_WINDOW (tooltip->window), text);
|
||
}
|
||
|
||
/**
|
||
* gtk_tooltip_set_icon:
|
||
* @tooltip: a #GtkTooltip
|
||
* @paintable: (allow-none): a #GdkPaintable, or %NULL
|
||
*
|
||
* Sets the icon of the tooltip (which is in front of the text) to be
|
||
* @paintable. If @paintable is %NULL, the image will be hidden.
|
||
*/
|
||
void
|
||
gtk_tooltip_set_icon (GtkTooltip *tooltip,
|
||
GdkPaintable *paintable)
|
||
{
|
||
g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
|
||
g_return_if_fail (paintable == NULL || GDK_IS_PAINTABLE (paintable));
|
||
|
||
gtk_tooltip_window_set_image_icon (GTK_TOOLTIP_WINDOW (tooltip->window), paintable);
|
||
}
|
||
|
||
/**
|
||
* gtk_tooltip_set_icon_from_icon_name:
|
||
* @tooltip: a #GtkTooltip
|
||
* @icon_name: (allow-none): an icon name, or %NULL
|
||
*
|
||
* 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.
|
||
*/
|
||
void
|
||
gtk_tooltip_set_icon_from_icon_name (GtkTooltip *tooltip,
|
||
const gchar *icon_name)
|
||
{
|
||
g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
|
||
|
||
gtk_tooltip_window_set_image_icon_from_name (GTK_TOOLTIP_WINDOW (tooltip->window),
|
||
icon_name);
|
||
}
|
||
|
||
/**
|
||
* gtk_tooltip_set_icon_from_gicon:
|
||
* @tooltip: a #GtkTooltip
|
||
* @gicon: (allow-none): a #GIcon representing the icon, or %NULL
|
||
*
|
||
* 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.
|
||
*/
|
||
void
|
||
gtk_tooltip_set_icon_from_gicon (GtkTooltip *tooltip,
|
||
GIcon *gicon)
|
||
{
|
||
g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
|
||
|
||
gtk_tooltip_window_set_image_icon_from_gicon (GTK_TOOLTIP_WINDOW (tooltip->window),
|
||
gicon);
|
||
}
|
||
|
||
/**
|
||
* gtk_tooltip_set_custom:
|
||
* @tooltip: a #GtkTooltip
|
||
* @custom_widget: (allow-none): a #GtkWidget, or %NULL to unset the old 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().
|
||
*/
|
||
void
|
||
gtk_tooltip_set_custom (GtkTooltip *tooltip,
|
||
GtkWidget *custom_widget)
|
||
{
|
||
g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
|
||
g_return_if_fail (custom_widget == NULL || 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;
|
||
|
||
gtk_tooltip_window_set_custom_widget (GTK_TOOLTIP_WINDOW (tooltip->window), custom_widget);
|
||
}
|
||
|
||
/**
|
||
* gtk_tooltip_set_tip_area:
|
||
* @tooltip: a #GtkTooltip
|
||
* @rect: a #GdkRectangle
|
||
*
|
||
* Sets the area of the widget, where the contents of this tooltip apply,
|
||
* to be @rect (in widget coordinates). This is especially useful for
|
||
* properly setting tooltips on #GtkTreeView rows and cells, #GtkIconViews,
|
||
* etc.
|
||
*
|
||
* For setting tooltips on #GtkTreeView, please refer to the convenience
|
||
* functions for this: gtk_tree_view_set_tooltip_row() and
|
||
* gtk_tree_view_set_tooltip_cell().
|
||
*/
|
||
void
|
||
gtk_tooltip_set_tip_area (GtkTooltip *tooltip,
|
||
const GdkRectangle *rect)
|
||
{
|
||
g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
|
||
|
||
if (!rect)
|
||
tooltip->tip_area_set = FALSE;
|
||
else
|
||
{
|
||
tooltip->tip_area_set = TRUE;
|
||
tooltip->tip_area = *rect;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* gtk_tooltip_trigger_tooltip_query:
|
||
* @display: a #GdkDisplay
|
||
*
|
||
* Triggers a new tooltip query on @display, in order to update the current
|
||
* visible tooltip, or to show/hide the current tooltip. This function is
|
||
* useful to call when, for example, the state of the widget changed by a
|
||
* key press.
|
||
*/
|
||
void
|
||
gtk_tooltip_trigger_tooltip_query (GdkDisplay *display)
|
||
{
|
||
gint x, y;
|
||
GdkSurface *surface;
|
||
GdkDevice *device;
|
||
|
||
/* Trigger logic as if the mouse moved */
|
||
device = gdk_seat_get_pointer (gdk_display_get_default_seat (display));
|
||
surface = gdk_device_get_surface_at_position (device, &x, &y);
|
||
if (!surface)
|
||
return;
|
||
|
||
gtk_tooltip_handle_event_internal (GDK_MOTION_NOTIFY, surface, x, y);
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_window_hide (GtkWidget *widget,
|
||
gpointer user_data)
|
||
{
|
||
GtkTooltip *tooltip = GTK_TOOLTIP (user_data);
|
||
|
||
gtk_tooltip_set_custom (tooltip, NULL);
|
||
}
|
||
|
||
GtkWidget *
|
||
_gtk_widget_find_at_coords (GdkSurface *surface,
|
||
gint surface_x,
|
||
gint surface_y,
|
||
gint *widget_x,
|
||
gint *widget_y)
|
||
{
|
||
GtkWidget *event_widget;
|
||
GtkWidget *picked_widget;
|
||
|
||
g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);
|
||
|
||
gdk_surface_get_user_data (surface, (void **)&event_widget);
|
||
|
||
if (!event_widget)
|
||
return NULL;
|
||
|
||
picked_widget = gtk_widget_pick (event_widget, surface_x, surface_y);
|
||
|
||
if (picked_widget != NULL)
|
||
gtk_widget_translate_coordinates (event_widget, picked_widget, surface_x, surface_y, widget_x, widget_y);
|
||
|
||
return picked_widget;
|
||
}
|
||
|
||
/* Ignores (x, y) on input, translates event coordinates to
|
||
* allocation relative (x, y) of the returned widget.
|
||
*/
|
||
static GtkWidget *
|
||
find_topmost_widget_coords (GdkSurface *surface,
|
||
gdouble dx,
|
||
gdouble dy,
|
||
gint *x,
|
||
gint *y)
|
||
{
|
||
GtkAllocation allocation;
|
||
gint tx, ty;
|
||
GtkWidget *tmp;
|
||
|
||
/* Returns coordinates relative to tmp's allocation. */
|
||
tmp = _gtk_widget_find_at_coords (surface, dx, dy, &tx, &ty);
|
||
|
||
if (!tmp)
|
||
return NULL;
|
||
|
||
/* Make sure the pointer can actually be on the widget returned. */
|
||
gtk_widget_get_allocation (tmp, &allocation);
|
||
allocation.x = 0;
|
||
allocation.y = 0;
|
||
if (GTK_IS_WINDOW (tmp))
|
||
{
|
||
GtkBorder border;
|
||
_gtk_window_get_shadow_width (GTK_WINDOW (tmp), &border);
|
||
allocation.x = border.left;
|
||
allocation.y = border.top;
|
||
allocation.width -= border.left + border.right;
|
||
allocation.height -= border.top + border.bottom;
|
||
}
|
||
|
||
if (tx < allocation.x || tx >= allocation.width ||
|
||
ty < allocation.y || ty >= allocation.height)
|
||
return NULL;
|
||
|
||
if (x)
|
||
*x = tx;
|
||
if (y)
|
||
*y = ty;
|
||
|
||
return tmp;
|
||
}
|
||
|
||
static gint
|
||
tooltip_browse_mode_expired (gpointer data)
|
||
{
|
||
GtkTooltip *tooltip;
|
||
GdkDisplay *display;
|
||
|
||
tooltip = GTK_TOOLTIP (data);
|
||
|
||
tooltip->browse_mode_enabled = FALSE;
|
||
tooltip->browse_mode_timeout_id = 0;
|
||
|
||
if (tooltip->timeout_id)
|
||
{
|
||
g_source_remove (tooltip->timeout_id);
|
||
tooltip->timeout_id = 0;
|
||
}
|
||
|
||
/* destroy tooltip */
|
||
display = gtk_widget_get_display (tooltip->window);
|
||
g_object_set_qdata (G_OBJECT (display), quark_current_tooltip, NULL);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_display_closed (GdkDisplay *display,
|
||
gboolean was_error,
|
||
GtkTooltip *tooltip)
|
||
{
|
||
if (tooltip->timeout_id)
|
||
{
|
||
g_source_remove (tooltip->timeout_id);
|
||
tooltip->timeout_id = 0;
|
||
}
|
||
|
||
g_object_set_qdata (G_OBJECT (display), quark_current_tooltip, NULL);
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_set_last_surface (GtkTooltip *tooltip,
|
||
GdkSurface *surface)
|
||
{
|
||
GtkWidget *window_widget = NULL;
|
||
|
||
if (tooltip->last_surface == surface)
|
||
return;
|
||
|
||
if (tooltip->last_surface)
|
||
g_object_remove_weak_pointer (G_OBJECT (tooltip->last_surface),
|
||
(gpointer *) &tooltip->last_surface);
|
||
|
||
tooltip->last_surface = surface;
|
||
|
||
if (tooltip->last_surface)
|
||
g_object_add_weak_pointer (G_OBJECT (tooltip->last_surface),
|
||
(gpointer *) &tooltip->last_surface);
|
||
|
||
if (surface)
|
||
gdk_surface_get_user_data (surface, (gpointer *) &window_widget);
|
||
|
||
if (window_widget)
|
||
window_widget = gtk_widget_get_toplevel (window_widget);
|
||
|
||
if (window_widget &&
|
||
window_widget != tooltip->window &&
|
||
gtk_widget_is_toplevel (window_widget) &&
|
||
GTK_IS_WINDOW (window_widget))
|
||
gtk_window_set_transient_for (GTK_WINDOW (tooltip->window),
|
||
GTK_WINDOW (window_widget));
|
||
else
|
||
gtk_window_set_transient_for (GTK_WINDOW (tooltip->window), NULL);
|
||
}
|
||
|
||
static gboolean
|
||
gtk_tooltip_run_requery (GtkWidget **widget,
|
||
GtkTooltip *tooltip,
|
||
gint *x,
|
||
gint *y)
|
||
{
|
||
gboolean has_tooltip = FALSE;
|
||
gboolean return_value = FALSE;
|
||
|
||
/* Reset tooltip */
|
||
gtk_tooltip_set_markup (tooltip, NULL);
|
||
gtk_tooltip_set_icon (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;
|
||
|
||
do
|
||
{
|
||
has_tooltip = gtk_widget_get_has_tooltip (*widget);
|
||
|
||
if (has_tooltip)
|
||
return_value = gtk_widget_query_tooltip (*widget, *x, *y, tooltip->keyboard_mode_enabled, tooltip);
|
||
|
||
if (!return_value)
|
||
{
|
||
GtkWidget *parent = gtk_widget_get_parent (*widget);
|
||
|
||
if (parent)
|
||
gtk_widget_translate_coordinates (*widget, parent, *x, *y, x, y);
|
||
|
||
*widget = parent;
|
||
}
|
||
else
|
||
break;
|
||
}
|
||
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
|
||
gtk_tooltip_position (GtkTooltip *tooltip,
|
||
GdkDisplay *display,
|
||
GtkWidget *new_tooltip_widget,
|
||
GdkDevice *device)
|
||
{
|
||
GtkSettings *settings;
|
||
GdkRectangle anchor_rect;
|
||
GdkSurface *surface;
|
||
GdkSurface *effective_toplevel;
|
||
GtkWidget *toplevel;
|
||
int rect_anchor_dx = 0;
|
||
int cursor_size;
|
||
int anchor_rect_padding;
|
||
|
||
gtk_widget_realize (GTK_WIDGET (tooltip->current_window));
|
||
surface = _gtk_widget_get_surface (GTK_WIDGET (tooltip->current_window));
|
||
|
||
tooltip->tooltip_widget = new_tooltip_widget;
|
||
|
||
toplevel = _gtk_widget_get_toplevel (new_tooltip_widget);
|
||
gtk_widget_translate_coordinates (new_tooltip_widget, toplevel,
|
||
0, 0,
|
||
&anchor_rect.x, &anchor_rect.y);
|
||
|
||
anchor_rect.width = gtk_widget_get_allocated_width (new_tooltip_widget);
|
||
anchor_rect.height = gtk_widget_get_allocated_height (new_tooltip_widget);
|
||
|
||
settings = gtk_settings_get_for_display (display);
|
||
g_object_get (settings,
|
||
"gtk-cursor-theme-size", &cursor_size,
|
||
NULL);
|
||
|
||
if (device)
|
||
anchor_rect_padding = MAX (4, cursor_size - 32);
|
||
else
|
||
anchor_rect_padding = 4;
|
||
|
||
anchor_rect.x -= anchor_rect_padding;
|
||
anchor_rect.y -= anchor_rect_padding;
|
||
anchor_rect.width += anchor_rect_padding * 2;
|
||
anchor_rect.height += anchor_rect_padding * 2;
|
||
|
||
if (device)
|
||
{
|
||
const int max_x_distance = 32;
|
||
/* Max 48x48 icon + default padding */
|
||
const int max_anchor_rect_height = 48 + 8;
|
||
int pointer_x, pointer_y;
|
||
|
||
/*
|
||
* For pointer position triggered tooltips, implement the following
|
||
* semantics:
|
||
*
|
||
* If the anchor rectangle is too tall (meaning if we'd be constrained
|
||
* and flip, it'd flip too far away), rely only on the pointer position
|
||
* to position the tooltip. The approximate pointer cursorrectangle is
|
||
* used as a anchor rectantgle.
|
||
*
|
||
* If the anchor rectangle isn't to tall, make sure the tooltip isn't too
|
||
* far away from the pointer position.
|
||
*/
|
||
effective_toplevel = _gtk_widget_get_surface (new_tooltip_widget);
|
||
gdk_surface_get_device_position (effective_toplevel,
|
||
device,
|
||
&pointer_x, &pointer_y, NULL);
|
||
|
||
if (anchor_rect.height > max_anchor_rect_height)
|
||
{
|
||
anchor_rect.x = pointer_x - 4;
|
||
anchor_rect.y = pointer_y - 4;
|
||
anchor_rect.width = cursor_size;
|
||
anchor_rect.height = cursor_size;
|
||
}
|
||
else
|
||
{
|
||
int anchor_point_x;
|
||
int x_distance;
|
||
|
||
anchor_point_x = anchor_rect.x + anchor_rect.width / 2;
|
||
x_distance = pointer_x - anchor_point_x;
|
||
|
||
if (x_distance > max_x_distance)
|
||
rect_anchor_dx = x_distance - max_x_distance;
|
||
else if (x_distance < -max_x_distance)
|
||
rect_anchor_dx = x_distance + max_x_distance;
|
||
}
|
||
}
|
||
|
||
gtk_window_set_transient_for (GTK_WINDOW (tooltip->current_window),
|
||
GTK_WINDOW (toplevel));
|
||
|
||
gdk_surface_move_to_rect (surface,
|
||
&anchor_rect,
|
||
GDK_GRAVITY_SOUTH,
|
||
GDK_GRAVITY_NORTH,
|
||
GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE_X,
|
||
rect_anchor_dx, 0);
|
||
gtk_widget_show (GTK_WIDGET (tooltip->current_window));
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_show_tooltip (GdkDisplay *display)
|
||
{
|
||
gint x, y;
|
||
GdkSurface *surface;
|
||
GtkWidget *tooltip_widget;
|
||
GdkDevice *device;
|
||
GtkTooltip *tooltip;
|
||
gboolean return_value = FALSE;
|
||
|
||
tooltip = g_object_get_qdata (G_OBJECT (display), quark_current_tooltip);
|
||
|
||
if (tooltip->keyboard_mode_enabled)
|
||
{
|
||
x = y = -1;
|
||
tooltip_widget = tooltip->keyboard_widget;
|
||
device = NULL;
|
||
}
|
||
else
|
||
{
|
||
gint tx, ty;
|
||
|
||
surface = tooltip->last_surface;
|
||
|
||
if (!GDK_IS_SURFACE (surface))
|
||
return;
|
||
|
||
device = gdk_seat_get_pointer (gdk_display_get_default_seat (display));
|
||
|
||
gdk_surface_get_device_position (surface, device, &x, &y, NULL);
|
||
|
||
gdk_surface_get_root_coords (surface, x, y, &tx, &ty);
|
||
tooltip_widget = _gtk_widget_find_at_coords (surface, x, y, &x, &y);
|
||
}
|
||
|
||
if (!tooltip_widget)
|
||
return;
|
||
|
||
return_value = gtk_tooltip_run_requery (&tooltip_widget, tooltip, &x, &y);
|
||
if (!return_value)
|
||
return;
|
||
|
||
if (!tooltip->current_window)
|
||
{
|
||
if (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);
|
||
}
|
||
|
||
/* FIXME: should use tooltip->current_window iso tooltip->window */
|
||
if (display != gtk_widget_get_display (tooltip->window))
|
||
{
|
||
g_signal_handlers_disconnect_by_func (display,
|
||
gtk_tooltip_display_closed,
|
||
tooltip);
|
||
|
||
gtk_window_set_display (GTK_WINDOW (tooltip->window), display);
|
||
|
||
g_signal_connect (display, "closed",
|
||
G_CALLBACK (gtk_tooltip_display_closed), tooltip);
|
||
}
|
||
|
||
gtk_tooltip_position (tooltip, display, tooltip_widget, device);
|
||
|
||
/* Now a tooltip is visible again on the display, make sure browse
|
||
* mode is enabled.
|
||
*/
|
||
tooltip->browse_mode_enabled = TRUE;
|
||
if (tooltip->browse_mode_timeout_id)
|
||
{
|
||
g_source_remove (tooltip->browse_mode_timeout_id);
|
||
tooltip->browse_mode_timeout_id = 0;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_hide_tooltip (GtkTooltip *tooltip)
|
||
{
|
||
if (!tooltip)
|
||
return;
|
||
|
||
if (tooltip->timeout_id)
|
||
{
|
||
g_source_remove (tooltip->timeout_id);
|
||
tooltip->timeout_id = 0;
|
||
}
|
||
|
||
if (!GTK_TOOLTIP_VISIBLE (tooltip))
|
||
return;
|
||
|
||
tooltip->tooltip_widget = NULL;
|
||
|
||
if (!tooltip->keyboard_mode_enabled)
|
||
{
|
||
guint timeout = BROWSE_DISABLE_TIMEOUT;
|
||
|
||
/* The tooltip is gone, after (by default, should be configurable) 500ms
|
||
* we want to turn off browse mode
|
||
*/
|
||
if (!tooltip->browse_mode_timeout_id)
|
||
{
|
||
tooltip->browse_mode_timeout_id =
|
||
g_timeout_add_full (0, timeout,
|
||
tooltip_browse_mode_expired,
|
||
g_object_ref (tooltip),
|
||
g_object_unref);
|
||
g_source_set_name_by_id (tooltip->browse_mode_timeout_id, "[gtk+] tooltip_browse_mode_expired");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (tooltip->browse_mode_timeout_id)
|
||
{
|
||
g_source_remove (tooltip->browse_mode_timeout_id);
|
||
tooltip->browse_mode_timeout_id = 0;
|
||
}
|
||
}
|
||
|
||
if (tooltip->current_window)
|
||
{
|
||
gtk_widget_hide (GTK_WIDGET (tooltip->current_window));
|
||
tooltip->current_window = NULL;
|
||
}
|
||
}
|
||
|
||
static gint
|
||
tooltip_popup_timeout (gpointer data)
|
||
{
|
||
GdkDisplay *display;
|
||
GtkTooltip *tooltip;
|
||
|
||
display = GDK_DISPLAY (data);
|
||
tooltip = g_object_get_qdata (G_OBJECT (display), quark_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->timeout_id = 0;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_start_delay (GdkDisplay *display)
|
||
{
|
||
guint timeout;
|
||
GtkTooltip *tooltip;
|
||
|
||
tooltip = g_object_get_qdata (G_OBJECT (display), quark_current_tooltip);
|
||
|
||
if (!tooltip || GTK_TOOLTIP_VISIBLE (tooltip))
|
||
return;
|
||
|
||
if (tooltip->timeout_id)
|
||
g_source_remove (tooltip->timeout_id);
|
||
|
||
if (tooltip->browse_mode_enabled)
|
||
timeout = BROWSE_TIMEOUT;
|
||
else
|
||
timeout = HOVER_TIMEOUT;
|
||
|
||
tooltip->timeout_id = g_timeout_add_full (0, timeout,
|
||
tooltip_popup_timeout,
|
||
g_object_ref (display),
|
||
g_object_unref);
|
||
g_source_set_name_by_id (tooltip->timeout_id, "[gtk+] tooltip_popup_timeout");
|
||
}
|
||
|
||
void
|
||
_gtk_tooltip_focus_in (GtkWidget *widget)
|
||
{
|
||
gint x = 0, y = 0;
|
||
gboolean return_value = FALSE;
|
||
GdkDisplay *display;
|
||
GtkTooltip *tooltip;
|
||
GdkDevice *device;
|
||
|
||
/* Get current tooltip for this display */
|
||
display = gtk_widget_get_display (widget);
|
||
tooltip = g_object_get_qdata (G_OBJECT (display), quark_current_tooltip);
|
||
|
||
/* Check if keyboard mode is enabled at this moment */
|
||
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_surface_get_device_position (gtk_widget_get_surface (widget),
|
||
device, &x, &y, NULL);
|
||
|
||
return_value = gtk_tooltip_run_requery (&widget, tooltip, &x, &y);
|
||
if (!return_value)
|
||
{
|
||
gtk_tooltip_hide_tooltip (tooltip);
|
||
return;
|
||
}
|
||
|
||
if (!tooltip->current_window)
|
||
{
|
||
if (gtk_widget_get_tooltip_window (widget))
|
||
tooltip->current_window = gtk_widget_get_tooltip_window (widget);
|
||
else
|
||
tooltip->current_window = GTK_WINDOW (GTK_TOOLTIP (tooltip)->window);
|
||
}
|
||
|
||
gtk_tooltip_show_tooltip (display);
|
||
}
|
||
|
||
void
|
||
_gtk_tooltip_focus_out (GtkWidget *widget)
|
||
{
|
||
GdkDisplay *display;
|
||
GtkTooltip *tooltip;
|
||
|
||
/* Get current tooltip for this display */
|
||
display = gtk_widget_get_display (widget);
|
||
tooltip = g_object_get_qdata (G_OBJECT (display), quark_current_tooltip);
|
||
|
||
if (!tooltip || !tooltip->keyboard_mode_enabled)
|
||
return;
|
||
|
||
if (tooltip->keyboard_widget)
|
||
{
|
||
g_object_unref (tooltip->keyboard_widget);
|
||
tooltip->keyboard_widget = NULL;
|
||
}
|
||
|
||
gtk_tooltip_hide_tooltip (tooltip);
|
||
}
|
||
|
||
void
|
||
_gtk_tooltip_toggle_keyboard_mode (GtkWidget *widget)
|
||
{
|
||
GdkDisplay *display;
|
||
GtkTooltip *tooltip;
|
||
|
||
display = gtk_widget_get_display (widget);
|
||
tooltip = g_object_get_qdata (G_OBJECT (display), quark_current_tooltip);
|
||
|
||
if (!tooltip)
|
||
{
|
||
tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
|
||
g_object_set_qdata_full (G_OBJECT (display),
|
||
quark_current_tooltip,
|
||
tooltip,
|
||
g_object_unref);
|
||
g_signal_connect (display, "closed",
|
||
G_CALLBACK (gtk_tooltip_display_closed),
|
||
tooltip);
|
||
}
|
||
|
||
tooltip->keyboard_mode_enabled ^= 1;
|
||
|
||
if (tooltip->keyboard_mode_enabled)
|
||
{
|
||
tooltip->keyboard_widget = g_object_ref (widget);
|
||
_gtk_tooltip_focus_in (widget);
|
||
}
|
||
else
|
||
{
|
||
if (tooltip->keyboard_widget)
|
||
{
|
||
g_object_unref (tooltip->keyboard_widget);
|
||
tooltip->keyboard_widget = NULL;
|
||
}
|
||
|
||
gtk_tooltip_hide_tooltip (tooltip);
|
||
}
|
||
}
|
||
|
||
void
|
||
_gtk_tooltip_hide (GtkWidget *widget)
|
||
{
|
||
GdkDisplay *display;
|
||
GtkTooltip *tooltip;
|
||
|
||
display = gtk_widget_get_display (widget);
|
||
tooltip = g_object_get_qdata (G_OBJECT (display), quark_current_tooltip);
|
||
|
||
if (!tooltip || !GTK_TOOLTIP_VISIBLE (tooltip) || !tooltip->tooltip_widget)
|
||
return;
|
||
|
||
if (widget == tooltip->tooltip_widget)
|
||
gtk_tooltip_hide_tooltip (tooltip);
|
||
}
|
||
|
||
static gboolean
|
||
tooltips_enabled (GdkEvent *event)
|
||
{
|
||
GdkDevice *source_device;
|
||
GdkInputSource source;
|
||
|
||
source_device = gdk_event_get_source_device (event);
|
||
|
||
if (!source_device)
|
||
return FALSE;
|
||
|
||
source = gdk_device_get_source (source_device);
|
||
|
||
if (source != GDK_SOURCE_TOUCHSCREEN)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
void
|
||
_gtk_tooltip_handle_event (GdkEvent *event)
|
||
{
|
||
GdkEventType event_type;
|
||
GdkSurface *surface;
|
||
gdouble dx, dy;
|
||
GdkModifierType event_state = 0;
|
||
|
||
if (!tooltips_enabled (event))
|
||
return;
|
||
|
||
gdk_event_get_state (event, &event_state);
|
||
if ((event_state &
|
||
(GDK_BUTTON1_MASK |
|
||
GDK_BUTTON2_MASK |
|
||
GDK_BUTTON3_MASK |
|
||
GDK_BUTTON4_MASK |
|
||
GDK_BUTTON5_MASK)) != 0)
|
||
return;
|
||
|
||
event_type = gdk_event_get_event_type (event);
|
||
surface = gdk_event_get_surface (event);
|
||
gdk_event_get_coords (event, &dx, &dy);
|
||
|
||
gtk_tooltip_handle_event_internal (event_type, surface, dx, dy);
|
||
}
|
||
|
||
static void
|
||
gtk_tooltip_handle_event_internal (GdkEventType event_type,
|
||
GdkSurface *surface,
|
||
gdouble dx,
|
||
gdouble dy)
|
||
{
|
||
int x = 0, y = 0;
|
||
GtkWidget *has_tooltip_widget;
|
||
GdkDisplay *display;
|
||
GtkTooltip *current_tooltip;
|
||
|
||
has_tooltip_widget = find_topmost_widget_coords (surface, dx, dy, &x, &y);
|
||
display = gdk_surface_get_display (surface);
|
||
current_tooltip = g_object_get_qdata (G_OBJECT (display), quark_current_tooltip);
|
||
|
||
if (current_tooltip)
|
||
gtk_tooltip_set_last_surface (current_tooltip, surface);
|
||
|
||
if (current_tooltip && current_tooltip->keyboard_mode_enabled)
|
||
{
|
||
gboolean return_value;
|
||
|
||
has_tooltip_widget = current_tooltip->keyboard_widget;
|
||
if (!has_tooltip_widget)
|
||
return;
|
||
|
||
return_value = gtk_tooltip_run_requery (&has_tooltip_widget,
|
||
current_tooltip,
|
||
&x, &y);
|
||
|
||
if (!return_value)
|
||
gtk_tooltip_hide_tooltip (current_tooltip);
|
||
else
|
||
gtk_tooltip_start_delay (display);
|
||
|
||
return;
|
||
}
|
||
|
||
/* Hide the tooltip when there's no new tooltip widget */
|
||
if (!has_tooltip_widget)
|
||
{
|
||
if (current_tooltip)
|
||
gtk_tooltip_hide_tooltip (current_tooltip);
|
||
|
||
return;
|
||
}
|
||
|
||
switch ((guint) event_type)
|
||
{
|
||
case GDK_BUTTON_PRESS:
|
||
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:
|
||
if (current_tooltip)
|
||
{
|
||
gboolean tip_area_set;
|
||
GdkRectangle tip_area;
|
||
gboolean hide_tooltip;
|
||
|
||
tip_area_set = current_tooltip->tip_area_set;
|
||
tip_area = current_tooltip->tip_area;
|
||
|
||
gtk_tooltip_run_requery (&has_tooltip_widget,
|
||
current_tooltip,
|
||
&x, &y);
|
||
|
||
/* 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;
|
||
|
||
/* Did the pointer move out of the previous "context area"? */
|
||
if (tip_area_set)
|
||
hide_tooltip |= (x <= tip_area.x
|
||
|| x >= tip_area.x + tip_area.width
|
||
|| y <= tip_area.y
|
||
|| y >= tip_area.y + tip_area.height);
|
||
|
||
if (hide_tooltip)
|
||
gtk_tooltip_hide_tooltip (current_tooltip);
|
||
else
|
||
gtk_tooltip_start_delay (display);
|
||
}
|
||
else
|
||
{
|
||
/* Need a new tooltip for this display */
|
||
current_tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
|
||
g_object_set_qdata_full (G_OBJECT (display),
|
||
quark_current_tooltip,
|
||
current_tooltip,
|
||
g_object_unref);
|
||
g_signal_connect (display, "closed",
|
||
G_CALLBACK (gtk_tooltip_display_closed),
|
||
current_tooltip);
|
||
|
||
gtk_tooltip_set_last_surface (current_tooltip, surface);
|
||
|
||
gtk_tooltip_start_delay (display);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|