From 0ef946ed64c746aa210f40884d5e082e9c3a6d01 Mon Sep 17 00:00:00 2001 From: Kristian Rietveld Date: Tue, 6 Feb 2007 10:25:21 +0000 Subject: [PATCH] New tooltips API. 2007-02-06 Kristian Rietveld New tooltips API. * gtk/Makefile.am * gtk/gtk.h * gtk/gtk.symbols: build system foo. * gtk/gtkmain.c (gtk_main_do_event): call tooltip event handler hook for appropriate events. * gtk/gtkmarshalers.list: add BOOLEAN:INT,INT,BOOLEAN,OBJECT. * gtk/gtkrc.c: add style for gtk-tooltip. * gtk/gtksettings.c (gtk_settings_class_init): make the different tooltip timeouts configurable. * gtk/gtkwidget.[ch]: add new properties, signals, make sure tooltips are hidden on unmap, destroy, update window event mask on realize, hook into focus change and show help handlers. * gtk/gtkwindow.h: move GtkWindow typdef to gtkwidget.h ... * gtk/gtktooltip.[ch]: new files. * tests/Makefile.am * tests/testtooltips.c: add test application. svn path=/trunk/; revision=17264 --- ChangeLog | 30 ++ gtk/Makefile.am | 2 + gtk/gtk.h | 1 + gtk/gtk.symbols | 14 + gtk/gtkmain.c | 15 + gtk/gtkmarshalers.list | 1 + gtk/gtkrc.c | 2 +- gtk/gtksettings.c | 70 +++ gtk/gtktooltip.c | 1064 ++++++++++++++++++++++++++++++++++++++++ gtk/gtktooltip.h | 55 +++ gtk/gtkwidget.c | 280 ++++++++++- gtk/gtkwidget.h | 15 +- gtk/gtkwindow.h | 1 - tests/Makefile.am | 8 +- tests/testtooltips.c | 377 ++++++++++++++ 15 files changed, 1928 insertions(+), 7 deletions(-) create mode 100644 gtk/gtktooltip.c create mode 100644 gtk/gtktooltip.h create mode 100644 tests/testtooltips.c diff --git a/ChangeLog b/ChangeLog index ee01fe0424..0ff9c9aadf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,33 @@ +2007-02-06 Kristian Rietveld + + New tooltips API. + + * gtk/Makefile.am + * gtk/gtk.h + * gtk/gtk.symbols: build system foo. + + * gtk/gtkmain.c (gtk_main_do_event): call tooltip event handler + hook for appropriate events. + + * gtk/gtkmarshalers.list: add BOOLEAN:INT,INT,BOOLEAN,OBJECT. + + * gtk/gtkrc.c: add style for gtk-tooltip. + + * gtk/gtksettings.c (gtk_settings_class_init): make the + different tooltip timeouts configurable. + + * gtk/gtkwidget.[ch]: add new properties, signals, make sure + tooltips are hidden on unmap, destroy, update window event + mask on realize, hook into focus change and show help + handlers. + + * gtk/gtkwindow.h: move GtkWindow typdef to gtkwidget.h ... + + * gtk/gtktooltip.[ch]: new files. + + * tests/Makefile.am + * tests/testtooltips.c: add test application. + 2007-02-05 Dom Lachowicz * modules/engines/ms-windows/msw_style.c: Fix bug 404506, caused diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 749b7ff8f5..b212d62ed7 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -292,6 +292,7 @@ gtk_public_h_sources = \ gtktoolbar.h \ gtktoolbutton.h \ gtktoolitem.h \ + gtktooltip.h \ gtktooltips.h \ gtktree.h \ gtktreednd.h \ @@ -559,6 +560,7 @@ gtk_base_c_sources = \ gtktoolbar.c \ gtktoolbutton.c \ gtktoolitem.c \ + gtktooltip.c \ gtktooltips.c \ gtktree.c \ gtktreedatalist.c \ diff --git a/gtk/gtk.h b/gtk/gtk.h index f4f927eda1..487f5eae13 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -177,6 +177,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index cabc850feb..1eedabd27b 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -3961,6 +3961,17 @@ gtk_tool_item_set_visible_vertical #endif #endif +#if IN_HEADER(__GTK_TOOLTIP_H__) +#if IN_FILE(__GTK_TOOLTIP_C__) +gtk_tooltip_get_type G_GNUC_CONST +gtk_tooltip_set_custom +gtk_tooltip_set_icon +gtk_tooltip_set_icon_from_stock +gtk_tooltip_set_markup +gtk_tooltip_trigger_tooltip_query +#endif +#endif + #if IN_HEADER(__GTK_TOOLTIPS_H__) #if IN_FILE(__GTK_TOOLTIPS_C__) gtk_tooltips_data_get @@ -4508,6 +4519,7 @@ gtk_widget_get_screen gtk_widget_get_settings gtk_widget_get_size_request gtk_widget_get_style +gtk_widget_get_tooltip_window gtk_widget_get_toplevel gtk_widget_get_type G_GNUC_CONST gtk_widget_get_visual @@ -4580,6 +4592,7 @@ gtk_widget_set_sensitive gtk_widget_set_size_request gtk_widget_set_state gtk_widget_set_style +gtk_widget_set_tooltip_window gtk_widget_shape_combine_mask gtk_widget_input_shape_combine_mask gtk_widget_show @@ -4592,6 +4605,7 @@ gtk_widget_style_get_property gtk_widget_style_get_valist gtk_widget_thaw_child_notify gtk_widget_translate_coordinates +gtk_widget_trigger_tooltip_query gtk_widget_unmap gtk_widget_unparent gtk_widget_unrealize diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index cbf393bf40..2480f72f35 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -65,6 +65,7 @@ #include "gtksettings.h" #include "gtkwidget.h" #include "gtkwindow.h" +#include "gtktooltip.h" #include "gtkprivate.h" #include "gtkdebug.h" #include "gtkalias.h" @@ -1593,6 +1594,20 @@ gtk_main_do_event (GdkEvent *event) g_assert_not_reached (); break; } + + if (event->type == GDK_ENTER_NOTIFY + || event->type == GDK_LEAVE_NOTIFY + || event->type == GDK_BUTTON_PRESS + || event->type == GDK_2BUTTON_PRESS + || event->type == GDK_3BUTTON_PRESS + || event->type == GDK_KEY_PRESS + || event->type == GDK_DRAG_ENTER + || event->type == GDK_GRAB_BROKEN + || event->type == GDK_MOTION_NOTIFY + || event->type == GDK_SCROLL) + { + _gtk_tooltip_handle_event (event); + } tmp_list = current_events; current_events = g_list_remove_link (current_events, tmp_list); diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index 175c149497..211bca5663 100644 --- a/gtk/gtkmarshalers.list +++ b/gtk/gtkmarshalers.list @@ -39,6 +39,7 @@ BOOLEAN:OBJECT,ENUM BOOLEAN:INT BOOLEAN:INT,INT BOOLEAN:INT,INT,INT +BOOLEAN:INT,INT,BOOLEAN,OBJECT BOOLEAN:UINT BOOLEAN:VOID BOOLEAN:BOOLEAN diff --git a/gtk/gtkrc.c b/gtk/gtkrc.c index 96c68cbce1..2ba6f3687a 100644 --- a/gtk/gtkrc.c +++ b/gtk/gtkrc.c @@ -893,7 +893,7 @@ _gtk_rc_init (void) "\n" "class \"GtkProgressBar\" style : gtk \"gtk-default-progress-bar-style\"\n" "class \"GtkTrayIcon\" style : gtk \"gtk-default-tray-icon-style\"\n" - "widget \"gtk-tooltips*\" style : gtk \"gtk-default-tooltips-style\"\n" + "widget \"gtk-tooltip*\" style : gtk \"gtk-default-tooltips-style\"\n" "widget_class \"**\" style : gtk \"gtk-default-menu-item-style\"\n" "widget_class \"**\" style : gtk \"gtk-default-menu-bar-item-style\"\n" "class \"GtkLabel\" style : gtk \"gtk-default-label-style\"\n" diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c index fb7858fbe5..8fdb459861 100644 --- a/gtk/gtksettings.c +++ b/gtk/gtksettings.c @@ -94,6 +94,9 @@ enum { PROP_COLOR_SCHEME, PROP_ENABLE_ANIMATIONS, PROP_TOUCHSCREEN_MODE, + PROP_TOOLTIP_TIMEOUT, + PROP_TOOLTIP_BROWSE_TIMEOUT, + PROP_TOOLTIP_BROWSE_MODE_TIMEOUT, PROP_KEYNAV_CURSOR_ONLY, PROP_KEYNAV_WRAP_AROUND, PROP_ERROR_BELL, @@ -556,6 +559,73 @@ gtk_settings_class_init (GtkSettingsClass *class) g_assert (result == PROP_TOUCHSCREEN_MODE); + /** + * GtkSettings:gtk-tooltip-timeout: + * + * Time, in milliseconds, after which a tooltip could appear if the + * cursor is hovering on top of a widget. + * + * Since: 2.12 + */ + result = settings_install_property_parser (class, + g_param_spec_int ("gtk-tooltip-timeout", + P_("Tooltip timeout"), + P_("Timeout before tooltip is shown"), + 0, G_MAXINT, + 1500, + GTK_PARAM_READWRITE), + NULL); + + g_assert (result == PROP_TOOLTIP_TIMEOUT); + + /** + * GtkSettings:gtk-tooltip-browse-timeout: + * + * Controls the time after which tooltips will appear when + * browse mode is enabled, in milliseconds. + * + * Browse mode is enabled when the mouse pointer moves off an object + * where a tooltip was currently being displayed. If the mouse pointer + * hits another object before the browse mode timeout expires (see + * gtk-tooltip-browse-mode-timeout), it will take the amount of + * milliseconds specified by this setting to popup the tooltip + * for the new object. + * + * Since: 2.12 + */ + result = settings_install_property_parser (class, + g_param_spec_int ("gtk-tooltip-browse-timeout", + P_("Tooltip browse timeout"), + P_("Timeout before tooltip is shown when browse mode is enabled"), + 0, G_MAXINT, + 100, + GTK_PARAM_READWRITE), + NULL); + + g_assert (result == PROP_TOOLTIP_BROWSE_TIMEOUT); + + /** + * GtkSettings:gtk-tooltip-browse-mode-timeout: + * + * Amount of time, in milliseconds, after which the browse mode + * will be disabled. + * + * See GtkSettings:gtk-tooltip-browse-timeout for more information + * about browse mode. + * + * Since: 2.12 + */ + result = settings_install_property_parser (class, + g_param_spec_int ("gtk-tooltip-browse-mode-timeout", + P_("Tooltip browse mode timeout"), + P_("Timeout after which browse mode is disabled"), + 0, G_MAXINT, + 500, + GTK_PARAM_READWRITE), + NULL); + + g_assert (result == PROP_TOOLTIP_BROWSE_MODE_TIMEOUT); + /** * GtkSettings:gtk-keynav-cursor-only: * diff --git a/gtk/gtktooltip.c b/gtk/gtktooltip.c new file mode 100644 index 0000000000..2e6f69a4f7 --- /dev/null +++ b/gtk/gtktooltip.c @@ -0,0 +1,1064 @@ +/* gtktooltip.c + * + * Copyright (C) 2006-2007 Imendio AB + * Contact: Kristian Rietveld + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include "gtktooltip.h" +#include "gtkintl.h" +#include "gtkwindow.h" +#include "gtkmain.h" +#include "gtklabel.h" +#include "gtkimage.h" +#include "gtkhbox.h" +#include "gtkalignment.h" + +#include + + +#define GTK_TOOLTIP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TOOLTIP, GtkTooltip)) +#define GTK_TOOLTIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TOOLTIP, GtkTooltipClass)) +#define GTK_IS_TOOLTIP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TOOLTIP)) +#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; + GtkWidget *alignment; + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; + GtkWidget *custom_widget; + + GtkWindow *current_window; + GtkWidget *keyboard_widget; + + GtkWidget *tooltip_widget; + GdkWindow *toplevel_window; + + gdouble last_x; + gdouble last_y; + GdkWindow *last_window; + + guint timeout_id; + guint browse_mode_timeout_id; + + guint browse_mode_enabled : 1; + guint keyboard_mode_enabled : 1; +}; + +struct _GtkTooltipClass +{ + GObjectClass parent_class; +}; + +#define GTK_TOOLTIP_VISIBLE(tooltip) ((tooltip)->current_window && GTK_WIDGET_VISIBLE ((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_window_hide (GtkWidget *widget, + gpointer user_data); +static void gtk_tooltip_display_closed (GdkDisplay *display, + gboolean was_error, + GtkTooltip *tooltip); + + +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->finalize = gtk_tooltip_finalize; +} + +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->toplevel_window = NULL; + + 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_box_pack_start (GTK_BOX (tooltip->box), tooltip->label, + FALSE, FALSE, 0); + + tooltip->custom_widget = NULL; +} + +static void +gtk_tooltip_finalize (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; + } + + 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); + } +} + +/* public API */ + +/** + * gtk_tooltip_set_markup: + * @label: a #GtkTooltip + * @markup: a markup string (see Pango markup format) or %NULL + * + * Sets the text of the tooltip to be @str, which is marked up + * with the Pango text markup language. + * If @markup is %NULL, the label will be hidden. + * + * Since: 2.12 + */ +void +gtk_tooltip_set_markup (GtkTooltip *tooltip, + const gchar *markup) +{ + g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); + + gtk_label_set_markup (GTK_LABEL (tooltip->label), markup); + + if (markup) + gtk_widget_show (tooltip->label); + else + gtk_widget_hide (tooltip->label); +} + +/** + * gtk_tooltip_set_icon: + * @tooltip: a #GtkTooltip + * @pixbuf: a #GdkPixbuf, or %NULL + * + * Sets the icon of the tooltip (which is in front of the text) to be + * @pixbuf. If @pixbuf is %NULL, the image will be hidden. + * + * Since: 2.12 + */ +void +gtk_tooltip_set_icon (GtkTooltip *tooltip, + GdkPixbuf *pixbuf) +{ + g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); + if (pixbuf) + g_return_if_fail (GDK_IS_PIXBUF (pixbuf)); + + gtk_image_set_from_pixbuf (GTK_IMAGE (tooltip->image), pixbuf); + + if (pixbuf) + gtk_widget_show (tooltip->image); + else + gtk_widget_hide (tooltip->image); +} + +/** + * gtk_tooltip_set_icon_from_stock: + * @tooltip: a #GtkTooltip + * @stock_id: a stock icon name, or %NULL + * @size: a stock icon size + * + * Sets the icon of the tooltip (which is in front of the text) to be + * the stock item indicated by @stock_id with the size indicated + * by @size. If @stock_id is %NULL, the image will be hidden. + * + * Since: 2.12 + */ +void +gtk_tooltip_set_icon_from_stock (GtkTooltip *tooltip, + const gchar *stock_id, + GtkIconSize size) +{ + g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); + + gtk_image_set_from_stock (GTK_IMAGE (tooltip->image), stock_id, size); + + if (stock_id) + gtk_widget_show (tooltip->image); + else + gtk_widget_hide (tooltip->image); +} + +/** + * gtk_tooltip_set_custom: + * tooltip: a #GtkTooltip + * custom_widget: a #GtkWidget + * + * Replaces the widget packed into the tooltip with @custom_widget. 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 + */ +void +gtk_tooltip_set_custom (GtkTooltip *tooltip, + GtkWidget *custom_widget) +{ + g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); + if (custom_widget) + g_return_if_fail (GTK_IS_WIDGET (custom_widget)); + + if (tooltip->custom_widget) + { + gtk_container_remove (GTK_CONTAINER (tooltip->box), + tooltip->custom_widget); + g_object_unref (tooltip->custom_widget); + } + + if (custom_widget) + { + tooltip->custom_widget = g_object_ref (custom_widget); + + gtk_container_add (GTK_CONTAINER (tooltip->box), custom_widget); + gtk_widget_show (custom_widget); + } + else + tooltip->custom_widget = NULL; +} + +/** + * gtk_tooltip_trigger_tooltip_query: + * @display: a #GtkDisplay + * + * 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. + * + * Since: 2.12 + */ +void +gtk_tooltip_trigger_tooltip_query (GdkDisplay *display) +{ + gint x, y; + GdkWindow *window; + GdkEvent event; + + /* Trigger logic as if the mouse moved */ + window = gdk_display_get_window_at_pointer (display, &x, &y); + if (!window) + return; + + event.type = GDK_MOTION_NOTIFY; + event.motion.window = window; + event.motion.x = x; + event.motion.y = y; + event.motion.is_hint = FALSE; + + _gtk_tooltip_handle_event (&event); +} + +/* private functions */ + +static void +gtk_tooltip_reset (GtkTooltip *tooltip) +{ + gtk_tooltip_set_markup (tooltip, NULL); + gtk_tooltip_set_icon (tooltip, NULL); + gtk_tooltip_set_custom (tooltip, NULL); +} + +static gboolean +gtk_tooltip_paint_window (GtkTooltip *tooltip) +{ + GtkRequisition req; + + gtk_widget_size_request (tooltip->window, &req); + 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); + + return FALSE; +} + +static void +gtk_tooltip_window_hide (GtkWidget *widget, + gpointer user_data) +{ + GtkTooltip *tooltip = GTK_TOOLTIP (user_data); + + if (tooltip->custom_widget) + gtk_tooltip_set_custom (tooltip, NULL); +} + +/* event handling, etc */ + +struct ChildLocation +{ + GtkWidget *child; + GtkWidget *container; + + gint x; + gint y; +}; + +static void +child_location_foreach (GtkWidget *child, + gpointer data) +{ + struct ChildLocation *child_loc = data; + + if (!child_loc->child) + { + gint x, y; + + gtk_widget_translate_coordinates (child_loc->container, child, + child_loc->x, child_loc->y, + &x, &y); + + if (x >= 0 && x < child->allocation.width + && y >= 0 && y < child->allocation.height) + { + if (GTK_IS_CONTAINER (child)) + { + struct ChildLocation tmp = { NULL, NULL, 0, 0 }; + + tmp.x = x; + tmp.y = y; + tmp.container = child; + + gtk_container_foreach (GTK_CONTAINER (child), + child_location_foreach, &tmp); + + if (tmp.child) + child_loc->child = tmp.child; + else + child_loc->child = child; + } + else + child_loc->child = child; + } + } +} + +static void +window_to_alloc (GtkWidget *dest_widget, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y) +{ + /* Translate from window relative to allocation relative */ + if (!GTK_WIDGET_NO_WINDOW (dest_widget) && dest_widget->parent) + { + gint wx, wy; + gdk_window_get_position (dest_widget->window, &wx, &wy); + + src_x += wx - dest_widget->allocation.x; + src_y += wy - dest_widget->allocation.y; + } + else + { + src_x -= dest_widget->allocation.x; + src_y -= dest_widget->allocation.y; + } + + if (dest_x) + *dest_x = src_x; + if (dest_y) + *dest_y = src_y; +} + +static GtkWidget * +find_widget_under_pointer (GdkWindow *window, + gint *x, + gint *y) +{ + GtkWidget *event_widget; + struct ChildLocation child_loc = { NULL, NULL, 0, 0 }; + + child_loc.x = *x; + child_loc.y = *y; + + gdk_window_get_user_data (window, (void **)&event_widget); + if (GTK_IS_CONTAINER (event_widget)) + { + window_to_alloc (event_widget, + child_loc.x, child_loc.y, + &child_loc.x, &child_loc.y); + + child_loc.container = event_widget; + child_loc.child = NULL; + + gtk_container_foreach (GTK_CONTAINER (event_widget), + child_location_foreach, &child_loc); + + if (child_loc.child) + event_widget = child_loc.child; + else if (child_loc.container) + event_widget = child_loc.container; + } + + if (x) + *x = child_loc.x; + if (y) + *y = child_loc.y; + + return event_widget; +} + +static GtkWidget * +find_topmost_widget_coords_from_event (GdkEvent *event, + gint *x, + gint *y) +{ + gint tx, ty; + gdouble dx, dy; + GtkWidget *tmp; + GtkWidget *orig; + gboolean has_tooltip; + + gdk_event_get_coords (event, &dx, &dy); + tx = dx; + ty = dy; + + orig = tmp = find_widget_under_pointer (event->any.window, &tx, &ty); + + g_object_get (tmp, "has-tooltip", &has_tooltip, NULL); + + if (tmp && (x != NULL || y != NULL)) + { + if (tmp != orig) + gtk_widget_translate_coordinates (orig, tmp, tx, ty, x, y); + else + { + if (x) + *x = tx; + if (y) + *y = ty; + } + } + + return tmp; +} + +static gint +tooltip_browse_mode_expired (gpointer data) +{ + GtkTooltip *tooltip; + + GDK_THREADS_ENTER (); + + tooltip = GTK_TOOLTIP (data); + + tooltip->browse_mode_enabled = FALSE; + tooltip->browse_mode_timeout_id = 0; + + /* destroy tooltip */ + g_object_set_data (G_OBJECT (gtk_widget_get_display (tooltip->window)), + "gdk-display-current-tooltip", NULL); + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +gtk_tooltip_display_closed (GdkDisplay *display, + gboolean was_error, + GtkTooltip *tooltip) +{ + g_object_set (display, "gdk-display-current-tooltip", NULL); +} + +static gboolean +gtk_tooltip_run_requery (GtkWidget **widget, + GtkTooltip *tooltip, + gint *x, + gint *y) +{ + gboolean has_tooltip = FALSE; + gboolean return_value = FALSE; + + gtk_tooltip_reset (tooltip); + + do + { + g_object_get (*widget, + "has-tooltip", &has_tooltip, + NULL); + + if (has_tooltip) + g_signal_emit_by_name (*widget, + "query-tooltip", + *x, *y, + tooltip->keyboard_mode_enabled, + tooltip, + &return_value); + + if (!return_value) + { + GtkWidget *parent = (*widget)->parent; + + if (parent) + gtk_widget_translate_coordinates (*widget, parent, *x, *y, x, y); + + *widget = parent; + } + else + break; + } + while (*widget); + + return return_value; +} + +static void +gtk_tooltip_show_tooltip (GdkDisplay *display) +{ + gint x, y; + gint w, h; + gint monitor_num; + GdkScreen *screen; + GdkRectangle monitor; + + 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"); + + if (tooltip->keyboard_mode_enabled) + { + pointer_widget = tooltip_widget = tooltip->keyboard_widget; + } + else + { + window = tooltip->last_window; + + gdk_window_get_origin (window, &x, &y); + x = tooltip->last_x - x; + y = tooltip->last_y - y; + + if (!window) + return; + + pointer_widget = tooltip_widget = find_widget_under_pointer (window, + &x, &y); + } + + if (!tooltip_widget) + return; + + 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; + + 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); + } + + /* Position the tooltip */ + /* FIXME: should we swap this when RTL is enabled? */ + if (tooltip->keyboard_mode_enabled) + { + gdk_window_get_origin (tooltip_widget->window, &x, &y); + if (GTK_WIDGET_NO_WINDOW (tooltip_widget)) + { + x += tooltip_widget->allocation.x; + y += tooltip_widget->allocation.y; + } + + /* For keyboard mode we position the tooltip below the widget, + * right of the center of the widget. + */ + x += tooltip_widget->allocation.width / 2; + y += tooltip_widget->allocation.height + 4; + } + else + { + guint cursor_size; + + x = tooltip->last_x; + y = tooltip->last_y; + + /* 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; + } + + if (tooltip->current_window) + { + GtkRequisition requisition; + + gtk_widget_size_request (GTK_WIDGET (tooltip->current_window), &requisition); + w = requisition.width; + h = requisition.height; + } + + screen = gtk_widget_get_screen (tooltip_widget); + + if (screen != gtk_widget_get_screen (tooltip->window)) + { + g_signal_handlers_disconnect_by_func (display, + 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); + } + + tooltip->tooltip_widget = tooltip_widget; + + monitor_num = gdk_screen_get_monitor_at_point (screen, x, y); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + if (x + w > monitor.x + monitor.width) + x -= x - (monitor.x + monitor.width) + w; + else if (x < monitor.x) + x = monitor.x; + + if (y + h > monitor.y + monitor.height) + y -= y - (monitor.y + monitor.height) + h; + + /* Show it */ + if (tooltip->current_window) + { + gtk_window_move (GTK_WINDOW (tooltip->current_window), x, y); + gtk_widget_show (GTK_WIDGET (tooltip->current_window)); + } + + /* 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 || !GTK_TOOLTIP_VISIBLE (tooltip)) + return; + + tooltip->tooltip_widget = NULL; + + if (tooltip->timeout_id) + { + g_source_remove (tooltip->timeout_id); + tooltip->timeout_id = 0; + } + + if (!tooltip->keyboard_mode_enabled) + { + guint timeout; + GtkSettings *settings; + + settings = gtk_widget_get_settings (GTK_WIDGET (tooltip->window)); + + g_object_get (settings, + "gtk-tooltip-browse-mode-timeout", &timeout, + NULL); + + /* 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); + } + 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; + + GDK_THREADS_ENTER (); + + display = GDK_DISPLAY_OBJECT (data); + + gtk_tooltip_show_tooltip (display); + + tooltip = g_object_get_data (G_OBJECT (display), + "gdk-display-current-tooltip"); + tooltip->timeout_id = 0; + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +gtk_tooltip_start_delay (GdkDisplay *display) +{ + guint timeout; + GtkTooltip *tooltip; + GtkSettings *settings; + + tooltip = g_object_get_data (G_OBJECT (display), + "gdk-display-current-tooltip"); + + if (tooltip && GTK_TOOLTIP_VISIBLE (tooltip)) + return; + + if (tooltip->timeout_id) + g_source_remove (tooltip->timeout_id); + + settings = gtk_widget_get_settings (GTK_WIDGET (tooltip->window)); + + if (tooltip->browse_mode_enabled) + g_object_get (settings, "gtk-tooltip-browse-timeout", &timeout, NULL); + else + g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL); + + tooltip->timeout_id = g_timeout_add_full (0, timeout, + tooltip_popup_timeout, + g_object_ref (display), + g_object_unref); +} + +void +_gtk_tooltip_focus_in (GtkWidget *widget) +{ + gint x, y; + gboolean return_value = FALSE; + GdkDisplay *display; + GtkTooltip *tooltip; + + /* Get current tooltip for this display */ + display = gtk_widget_get_display (widget); + tooltip = g_object_get_data (G_OBJECT (display), + "gdk-display-current-tooltip"); + + /* Check if keyboard mode is enabled at this moment */ + if (!tooltip || !tooltip->keyboard_mode_enabled) + 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); + + 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_data (G_OBJECT (display), + "gdk-display-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_data (G_OBJECT (display), + "gdk-display-current-tooltip"); + + if (!tooltip) + { + tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL); + g_object_set_data_full (G_OBJECT (display), + "gdk-display-current-tooltip", + tooltip, g_object_unref); + } + + 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) +{ + GtkWidget *toplevel; + GdkDisplay *display; + GtkTooltip *tooltip; + + display = gtk_widget_get_display (widget); + tooltip = g_object_get_data (G_OBJECT (display), + "gdk-display-current-tooltip"); + + if (!tooltip || !GTK_TOOLTIP_VISIBLE (tooltip) || !tooltip->tooltip_widget) + return; + + toplevel = gtk_widget_get_toplevel (widget); + + if (widget == tooltip->tooltip_widget + || toplevel->window == tooltip->toplevel_window) + gtk_tooltip_hide_tooltip (tooltip); +} + +void +_gtk_tooltip_handle_event (GdkEvent *event) +{ + gint x, y; + gboolean return_value = FALSE; + GtkWidget *has_tooltip_widget = NULL; + GdkDisplay *display; + GtkTooltip *current_tooltip; + + has_tooltip_widget = find_topmost_widget_coords_from_event (event, &x, &y); + display = gdk_drawable_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); + } + + if (current_tooltip && current_tooltip->keyboard_mode_enabled) + { + 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; + } + + /* Always poll for a next motion event */ + if (event->type == GDK_MOTION_NOTIFY && event->motion.is_hint) + gdk_window_get_pointer (event->any.window, NULL, NULL, NULL); + + /* Hide the tooltip when there's no new tooltip widget */ + if (!has_tooltip_widget) + { + if (current_tooltip && GTK_TOOLTIP_VISIBLE (current_tooltip)) + gtk_tooltip_hide_tooltip (current_tooltip); + + return; + } + + switch (event->type) + { + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_KEY_PRESS: + case GDK_DRAG_ENTER: + case GDK_GRAB_BROKEN: + 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) + { + 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); + } + else + { + /* Need a new tooltip for this display */ + current_tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL); + g_object_set_data_full (G_OBJECT (display), + "gdk-display-current-tooltip", + current_tooltip, g_object_unref); + + current_tooltip->last_window = event->any.window; + gdk_event_get_root_coords (event, + ¤t_tooltip->last_x, + ¤t_tooltip->last_y); + + gtk_tooltip_start_delay (display); + } + break; + + default: + break; + } +} diff --git a/gtk/gtktooltip.h b/gtk/gtktooltip.h new file mode 100644 index 0000000000..69b53e7670 --- /dev/null +++ b/gtk/gtktooltip.h @@ -0,0 +1,55 @@ +/* gtktooltip.h + * + * Copyright (C) 2006-2007 Imendio AB + * Contact: Kristian Rietveld + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_TOOLTIP__ +#define __GTK_TOOLTIP__ + +#include "gtkwidget.h" +#include "gtkwindow.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_TOOLTIP (gtk_tooltip_get_type ()) + +GType gtk_tooltip_get_type (void); + +void gtk_tooltip_set_markup (GtkTooltip *tooltip, + const gchar *markup); +void gtk_tooltip_set_icon (GtkTooltip *tooltip, + GdkPixbuf *pixbuf); +void gtk_tooltip_set_icon_from_stock (GtkTooltip *tooltip, + const gchar *stock_id, + GtkIconSize size); +void gtk_tooltip_set_custom (GtkTooltip *tooltip, + GtkWidget *custom_widget); + +void gtk_tooltip_trigger_tooltip_query (GdkDisplay *display); + + +void _gtk_tooltip_focus_in (GtkWidget *widget); +void _gtk_tooltip_focus_out (GtkWidget *widget); +void _gtk_tooltip_toggle_keyboard_mode (GtkWidget *widget); +void _gtk_tooltip_handle_event (GdkEvent *event); +void _gtk_tooltip_hide (GtkWidget *widget); + +G_END_DECLS + +#endif /* __GTK_TOOLTIP__ */ diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index b92c234573..93553afd48 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -50,6 +50,7 @@ #include "gdk/gdkkeysyms.h" #include "gtkaccessible.h" #include "gtktooltips.h" +#include "gtktooltip.h" #include "gtkinvisible.h" #include "gtkalias.h" @@ -121,6 +122,7 @@ enum { CAN_ACTIVATE_ACCEL, GRAB_BROKEN, COMPOSITED_CHANGED, + QUERY_TOOLTIP, KEYNAV_FAILED, DRAG_FAILED, LAST_SIGNAL @@ -145,7 +147,9 @@ enum { PROP_STYLE, PROP_EVENTS, PROP_EXTENSION_EVENTS, - PROP_NO_SHOW_ALL + PROP_NO_SHOW_ALL, + PROP_HAS_TOOLTIP, + PROP_TOOLTIP_MARKUP }; typedef struct _GtkStateData GtkStateData; @@ -158,7 +162,6 @@ struct _GtkStateData guint use_forall : 1; }; - /* --- prototypes --- */ static void gtk_widget_class_init (GtkWidgetClass *klass); static void gtk_widget_base_class_finalize (GtkWidgetClass *klass); @@ -190,6 +193,11 @@ static void gtk_widget_direction_changed (GtkWidget *widget, GtkTextDirection previous_direction); static void gtk_widget_real_grab_focus (GtkWidget *focus_widget); +static gboolean gtk_widget_real_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip); static gboolean gtk_widget_real_show_help (GtkWidget *widget, GtkWidgetHelpType help_type); @@ -230,6 +238,10 @@ static GdkScreen * gtk_widget_get_screen_unchecked (GtkWidget static void gtk_widget_queue_shallow_draw (GtkWidget *widget); static gboolean gtk_widget_real_can_activate_accel (GtkWidget *widget, guint signal_id); + +static void gtk_widget_set_has_tooltip (GtkWidget *widget, + gboolean has_tooltip, + gboolean force); static void gtk_widget_set_usize_internal (GtkWidget *widget, gint width, @@ -261,6 +273,9 @@ static GQuark quark_pango_context = 0; static GQuark quark_rc_style = 0; static GQuark quark_accessible_object = 0; static GQuark quark_mnemonic_labels = 0; +static GQuark quark_tooltip_markup = 0; +static GQuark quark_has_tooltip = 0; +static GQuark quark_tooltip_window = 0; GParamSpecPool *_gtk_widget_child_property_pool = NULL; GObjectNotifyContext *_gtk_widget_child_property_notify_context = NULL; @@ -336,6 +351,9 @@ gtk_widget_class_init (GtkWidgetClass *klass) quark_rc_style = g_quark_from_static_string ("gtk-rc-style"); quark_accessible_object = g_quark_from_static_string ("gtk-accessible-object"); quark_mnemonic_labels = g_quark_from_static_string ("gtk-mnemonic-labels"); + quark_tooltip_markup = g_quark_from_static_string ("gtk-tooltip-markup"); + quark_has_tooltip = g_quark_from_static_string ("gtk-has-tooltip"); + quark_tooltip_window = g_quark_from_static_string ("gtk-tooltip-window"); style_property_spec_pool = g_param_spec_pool_new (FALSE); _gtk_widget_child_property_pool = g_param_spec_pool_new (TRUE); @@ -407,6 +425,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) klass->screen_changed = NULL; klass->can_activate_accel = gtk_widget_real_can_activate_accel; klass->grab_broken_event = NULL; + klass->query_tooltip = gtk_widget_real_query_tooltip; klass->show_help = gtk_widget_real_show_help; @@ -548,6 +567,47 @@ gtk_widget_class_init (GtkWidgetClass *klass) P_("Whether gtk_widget_show_all() should not affect this widget"), FALSE, GTK_PARAM_READWRITE)); + +/** + * GtkWidget:has-tooltip: + * + * Enables or disables the emission of GtkWidget::query-tooltip on @widget. A + * value of %TRUE indicates that @widget can have a tooltip, in this case + * the widget will be queried using GtkWidget::query-tooltip to determine + * whether it will provide a tooltip or not. + * + * Since: 2.12 + */ + g_object_class_install_property (gobject_class, + PROP_HAS_TOOLTIP, + g_param_spec_boolean ("has-tooltip", + P_("Has tooltip"), + P_("Whether this widget has a tooltip"), + FALSE, + GTK_PARAM_READWRITE)); + +/** + * GtkWidget:tooltip-markup: + * + * Sets the text of tooltip to be the given string, which is marked up + * with the Pango text markup language. + * Also see gtk_tooltip_set_markup(). + * + * This is a convenience property which will take care of getting the + * tooltip shown if the given string is not %NULL: GtkWidget:has-tooltip + * will automatically be set to %TRUE and there will be taken care of + * GtkWidget::query-tooltip in the default signal handler. + * + * Since: 2.12 + */ + g_object_class_install_property (gobject_class, + PROP_TOOLTIP_MARKUP, + g_param_spec_string ("tooltip-markup", + P_("Tooltip markup"), + P_("The contents of the tooltip for this widget"), + NULL, + GTK_PARAM_READWRITE)); + widget_signals[SHOW] = g_signal_new (I_("show"), G_TYPE_FROM_CLASS (gobject_class), @@ -1450,6 +1510,45 @@ gtk_widget_class_init (GtkWidgetClass *klass) _gtk_marshal_BOOLEAN__BOXED, G_TYPE_BOOLEAN, 1, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + /** + * GtkWidget::query-tooltip: + * @widget: the object which received the signal + * @x: the x coordinate of the cursor position where the request has been + * emitted, relative to the widget's allocation + * @y: the y coordinate of the cursor position where the request has been + * emitted, relative to the widget's allocation + * @keyboard_mode: %TRUE if the tooltip was trigged using the keyboard + * @tooltip: a #GtkTooltip + * + * Emitted when the gtk-tooltip-timeout has expired with the cursor + * hovering "above" @widget; or emitted when @widget got focus in + * keyboard mode. + * + * Using the given coordinates, the signal handler should determine + * whether a tooltip should be shown for @widget. If this is the case + * %TRUE should be returned, %FALSE otherwise. Note that if + * @keyboard_mode is %TRUE, the values of @x and @y are undefined and + * should not be used. + * + * The signal handler is free to manipulate @tooltip with the therefore + * destined function calls. + * + * Returns: %TRUE if @tooltip should be shown right now, %FALSE otherwise. + * + * Since: 2.12 + */ + widget_signals[QUERY_TOOLTIP] = + g_signal_new (I_("query-tooltip"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkWidgetClass, query_tooltip), + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__INT_INT_BOOLEAN_OBJECT, + G_TYPE_BOOLEAN, 4, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_BOOLEAN, + GTK_TYPE_TOOLTIP); /** * GtkWidget::popup-menu * @widget: the object which received the signal @@ -1730,7 +1829,10 @@ gtk_widget_set_property (GObject *object, switch (prop_id) { + gboolean tmp; guint32 saved_flags; + gchar *tooltip_markup; + GtkWindow *tooltip_window; case PROP_NAME: gtk_widget_set_name (widget, g_value_get_string (value)); @@ -1805,6 +1907,21 @@ gtk_widget_set_property (GObject *object, case PROP_NO_SHOW_ALL: gtk_widget_set_no_show_all (widget, g_value_get_boolean (value)); break; + case PROP_HAS_TOOLTIP: + gtk_widget_set_has_tooltip (widget, g_value_get_boolean (value), FALSE); + break; + case PROP_TOOLTIP_MARKUP: + tooltip_markup = g_object_get_qdata (object, quark_tooltip_markup); + tooltip_window = g_object_get_qdata (object, quark_tooltip_window); + + tooltip_markup = g_value_dup_string (value); + + g_object_set_qdata_full (object, quark_tooltip_markup, + tooltip_markup, g_free); + + tmp = (tooltip_window != NULL || tooltip_markup != NULL); + gtk_widget_set_has_tooltip (widget, tmp, FALSE); + break; default: break; } @@ -1899,6 +2016,12 @@ gtk_widget_get_property (GObject *object, case PROP_NO_SHOW_ALL: g_value_set_boolean (value, gtk_widget_get_no_show_all (widget)); break; + case PROP_HAS_TOOLTIP: + g_value_set_boolean (value, GPOINTER_TO_UINT (g_object_get_qdata (object, quark_has_tooltip))); + break; + case PROP_TOOLTIP_MARKUP: + g_value_set_string (value, g_object_get_qdata (object, quark_tooltip_markup)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2490,6 +2613,7 @@ gtk_widget_unmap (GtkWidget *widget) { if (GTK_WIDGET_NO_WINDOW (widget)) gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE); + _gtk_tooltip_hide (widget); g_signal_emit (widget, widget_signals[UNMAP], 0); } } @@ -2548,7 +2672,9 @@ gtk_widget_realize (GtkWidget *widget) gtk_widget_ensure_style (widget); g_signal_emit (widget, widget_signals[REALIZE], 0); - + + gtk_widget_set_has_tooltip (widget, GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip)), TRUE); + if (GTK_WIDGET_HAS_SHAPE_MASK (widget)) { shape_info = g_object_get_qdata (G_OBJECT (widget), quark_shape_info); @@ -2601,6 +2727,7 @@ gtk_widget_unrealize (GtkWidget *widget) if (GTK_WIDGET_REALIZED (widget)) { g_object_ref (widget); + _gtk_tooltip_hide (widget); g_signal_emit (widget, widget_signals[UNREALIZE], 0); GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED | GTK_MAPPED); g_object_unref (widget); @@ -3897,6 +4024,7 @@ gtk_widget_event_internal (GtkWidget *widget, break; case GDK_DESTROY: signal_num = DESTROY_EVENT; + _gtk_tooltip_hide (widget); break; case GDK_KEY_PRESS: signal_num = KEY_PRESS_EVENT; @@ -3912,6 +4040,10 @@ gtk_widget_event_internal (GtkWidget *widget, break; case GDK_FOCUS_CHANGE: signal_num = event->focus_change.in ? FOCUS_IN_EVENT : FOCUS_OUT_EVENT; + if (event->focus_change.in) + _gtk_tooltip_focus_in (widget); + else + _gtk_tooltip_focus_out (widget); break; case GDK_CONFIGURE: signal_num = CONFIGURE_EVENT; @@ -4354,6 +4486,28 @@ gtk_widget_real_grab_focus (GtkWidget *focus_widget) } } +static gboolean +gtk_widget_real_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip) +{ + gchar *tooltip_markup; + gboolean has_tooltip; + + tooltip_markup = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_markup); + has_tooltip = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip)); + + if (has_tooltip && tooltip_markup) + { + gtk_tooltip_set_markup (tooltip, tooltip_markup); + return TRUE; + } + + return FALSE; +} + static gboolean gtk_widget_real_show_help (GtkWidget *widget, GtkWidgetHelpType help_type) @@ -4361,6 +4515,8 @@ gtk_widget_real_show_help (GtkWidget *widget, if (help_type == GTK_WIDGET_HELP_TOOLTIP) { _gtk_tooltips_toggle_keyboard_mode (widget); + _gtk_tooltip_toggle_keyboard_mode (widget); + return TRUE; } else @@ -5256,6 +5412,7 @@ do_screen_change (GtkWidget *widget, g_object_set_qdata (G_OBJECT (widget), quark_pango_context, NULL); } + _gtk_tooltip_hide (widget); g_signal_emit (widget, widget_signals[SCREEN_CHANGED], 0, old_screen); } } @@ -8219,5 +8376,122 @@ gtk_widget_set_no_show_all (GtkWidget *widget, g_object_notify (G_OBJECT (widget), "no-show-all"); } + +static void +gtk_widget_set_has_tooltip (GtkWidget *widget, + gboolean has_tooltip, + gboolean force) +{ + gboolean priv_has_tooltip; + + priv_has_tooltip = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget), + quark_has_tooltip)); + + if (priv_has_tooltip != has_tooltip || force) + { + priv_has_tooltip = has_tooltip; + + if (priv_has_tooltip) + { + if (GTK_WIDGET_REALIZED (widget) && GTK_WIDGET_NO_WINDOW (widget)) + gdk_window_set_events (widget->window, + gdk_window_get_events (widget->window) | + GDK_LEAVE_NOTIFY_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK); + + if (!GTK_WIDGET_NO_WINDOW (widget)) + gtk_widget_add_events (widget, + GDK_LEAVE_NOTIFY_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK); + } + + g_object_set_qdata (G_OBJECT (widget), quark_has_tooltip, + GUINT_TO_POINTER (priv_has_tooltip)); + } +} + +/** + * gtk_widget_set_tooltip_window: + * @widget: a #GtkWidget + * @custom_window: a #GtkWindow, or %NULL + * + * Replaces the default, usually yellow, window used for displaying + * tooltips with @custom_window. GTK+ will take care of showing and + * hiding @custom_window at the right moment, to behave likewise as + * the default tooltip window. If @custom_window is %NULL, the default + * tooltip window will be used. + * + * Since: 2.12 + */ +void +gtk_widget_set_tooltip_window (GtkWidget *widget, + GtkWindow *custom_window) +{ + gboolean tmp; + gchar *tooltip_markup; + GtkWindow *tooltip_window; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + if (custom_window) + g_return_if_fail (GTK_IS_WINDOW (custom_window)); + + tooltip_window = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_window); + tooltip_markup = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_markup); + + if (custom_window) + g_object_ref (custom_window); + + if (tooltip_window) + g_object_unref (tooltip_window); + + tooltip_window = custom_window; + g_object_set_qdata_full (G_OBJECT (widget), quark_tooltip_window, + tooltip_window, g_object_unref); + + tmp = (tooltip_window != NULL || tooltip_markup != NULL); + gtk_widget_set_has_tooltip (widget, tmp, FALSE); + + if (tmp) + gtk_widget_trigger_tooltip_query (widget); +} + +/** + * gtk_widget_get_tooltip_window: + * @widget: a #GtkWidget + * + * Returns the #GtkWindow of the current tooltip. This can be the + * GtkWindow created by default, or the custom tooltip window set + * using gtk_widget_set_tooltip_window(). + * + * Return value: The #GtkWindow of the current tooltip. + * + * Since: 2.12 + */ +GtkWindow * +gtk_widget_get_tooltip_window (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + return g_object_get_qdata (G_OBJECT (widget), quark_tooltip_window); +} + +/** + * gtk_widget_trigger_tooltip_query: + * @widget: a #GtkWidget + * + * Triggers a tooltip query on the display where the toplevel of @widget + * is located. See gtk_tooltip_trigger_tooltip_query() for more + * information. + * + * Since: 2.12 + */ +void +gtk_widget_trigger_tooltip_query (GtkWidget *widget) +{ + gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (widget)); +} + #define __GTK_WIDGET_C__ #include "gtkaliasdef.c" diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index cb5841fbd3..50374cc85a 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -140,6 +140,8 @@ typedef struct _GtkWidgetClass GtkWidgetClass; typedef struct _GtkWidgetAuxInfo GtkWidgetAuxInfo; typedef struct _GtkWidgetShapeInfo GtkWidgetShapeInfo; typedef struct _GtkClipboard GtkClipboard; +typedef struct _GtkTooltip GtkTooltip; +typedef struct _GtkWindow GtkWindow; typedef void (*GtkCallback) (GtkWidget *widget, gpointer data); @@ -409,8 +411,13 @@ struct _GtkWidgetClass void (* composited_changed) (GtkWidget *widget); + gboolean (* query_tooltip) (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tooltip, + GtkTooltip *tooltip); + /* Padding for future expansion */ - void (*_gtk_reserved4) (void); void (*_gtk_reserved5) (void); void (*_gtk_reserved6) (void); void (*_gtk_reserved7) (void); @@ -777,6 +784,12 @@ void gtk_widget_add_mnemonic_label (GtkWidget *widget, void gtk_widget_remove_mnemonic_label (GtkWidget *widget, GtkWidget *label); +void gtk_widget_set_tooltip_window (GtkWidget *widget, + GtkWindow *custom_window); +GtkWindow *gtk_widget_get_tooltip_window (GtkWidget *widget); +void gtk_widget_trigger_tooltip_query (GtkWidget *widget); + + GType gtk_requisition_get_type (void) G_GNUC_CONST; GtkRequisition *gtk_requisition_copy (const GtkRequisition *requisition); void gtk_requisition_free (GtkRequisition *requisition); diff --git a/gtk/gtkwindow.h b/gtk/gtkwindow.h index f022bb9eb1..4d4fc53d7e 100644 --- a/gtk/gtkwindow.h +++ b/gtk/gtkwindow.h @@ -44,7 +44,6 @@ G_BEGIN_DECLS #define GTK_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_WINDOW, GtkWindowClass)) -typedef struct _GtkWindow GtkWindow; typedef struct _GtkWindowClass GtkWindowClass; typedef struct _GtkWindowGeometryInfo GtkWindowGeometryInfo; typedef struct _GtkWindowGroup GtkWindowGroup; diff --git a/tests/Makefile.am b/tests/Makefile.am index 6972165a03..3058726ad2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -82,7 +82,8 @@ noinst_PROGRAMS = \ pixbuf-threads \ testmerge \ testactions \ - testgrouping + testgrouping \ + testtooltips autotestfilechooser_DEPENDENCIES = $(TEST_DEPS) simple_DEPENDENCIES = $(TEST_DEPS) @@ -134,6 +135,7 @@ testxinerama_DEPENDENCIES = $(TEST_DEPS) testmerge_DEPENDENCIES = $(TEST_DEPS) testactions_DEPENDENCIES = $(TEST_DEPS) testgrouping_DEPENDENCIES = $(TEST_DEPS) +testtooltips_DEPENDENCIES = $(TEST_DEPS) autotestfilechooser_LDADD = $(LDADDS) simple_LDADD = $(LDADDS) @@ -192,6 +194,7 @@ pixbuf_threads_LDADD = $(LDADDS) $(GLIB_LIBS) testmerge_LDADD = $(LDADDS) testactions_LDADD = $(LDADDS) testgrouping_LDADD = $(LDADDS) +testtooltips_LDADD = $(LDADDS) autotestfilechooser_SOURCES = \ autotestfilechooser.c @@ -267,6 +270,9 @@ testrecentchooser_SOURCES = \ testgrouping_SOURCES = \ testgrouping.c +testtoooltips_SOURCES = \ + testtooltips.c + EXTRA_DIST = \ prop-editor.h \ testgtk.1 \ diff --git a/tests/testtooltips.c b/tests/testtooltips.c new file mode 100644 index 0000000000..8def5111b7 --- /dev/null +++ b/tests/testtooltips.c @@ -0,0 +1,377 @@ +/* testtooltips.c: Test application for GTK+ >= 2.12 tooltips code + * + * Copyright (C) 2006-2007 Imendio AB + * Contact: Kristian Rietveld + * + * This work is provided "as is"; redistribution and modification + * in whole or in part, in any medium, physical or electronic is + * permitted without restriction. + * + * This work 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. + * + * In no event shall the authors or contributors be liable for any + * direct, indirect, incidental, special, exemplary, or consequential + * damages (including, but not limited to, procurement of substitute + * goods or services; loss of use, data, or profits; or business + * interruption) however caused and on any theory of liability, whether + * in contract, strict liability, or tort (including negligence or + * otherwise) arising in any way out of the use of this software, even + * if advised of the possibility of such damage. + */ + +#include + +static gboolean +query_tooltip_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer data) +{ + gtk_tooltip_set_markup (tooltip, gtk_button_get_label (GTK_BUTTON (widget))); + gtk_tooltip_set_icon_from_stock (tooltip, GTK_STOCK_DELETE, + GTK_ICON_SIZE_MENU); + + return TRUE; +} + +static gboolean +query_tooltip_custom_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer data) +{ + GdkColor color = { 0, 0, 65535 }; + GtkWindow *window = gtk_widget_get_tooltip_window (widget); + + gtk_widget_modify_bg (GTK_WIDGET (window), GTK_STATE_NORMAL, &color); + + return TRUE; +} + +static gboolean +query_tooltip_text_view_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer data) +{ + gint bx, by, trailing; + GtkTextTag *tag = data; + GtkTextIter iter; + GtkTextView *text_view = GTK_TEXT_VIEW (widget); + + gtk_text_view_window_to_buffer_coords (text_view, GTK_TEXT_WINDOW_TEXT, + x, y, &bx, &by); + gtk_text_view_get_iter_at_position (text_view, &iter, &trailing, bx, by); + + if (gtk_text_iter_has_tag (&iter, tag)) + gtk_tooltip_set_markup (tooltip, "Tooltip on text tag"); + else + return FALSE; + + return TRUE; +} + +static gboolean +query_tooltip_tree_view_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer data) +{ + GtkTreeIter iter; + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + GtkTreePath *path = NULL; + gchar *tmp; + gchar *pathstring; + + char buffer[512]; + + if (keyboard_tip) + { + /* Keyboard mode */ + gtk_tree_view_get_cursor (tree_view, &path, NULL); + + if (!path) + return FALSE; + } + else + { + /* Mouse mode */ + if (!gtk_tree_view_get_path_at_pos (tree_view, x, y, &path, NULL, NULL, NULL)) + return FALSE; + } + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, 0, &tmp, -1); + pathstring = gtk_tree_path_to_string (path); + + snprintf (buffer, 511, "Path %s: %s", pathstring, tmp); + gtk_tooltip_set_markup (tooltip, buffer); + + gtk_tree_path_free (path); + g_free (pathstring); + g_free (tmp); + + return TRUE; +} + +static GtkTreeModel * +create_model (void) +{ + GtkTreeStore *store; + GtkTreeIter iter; + + store = gtk_tree_store_new (1, G_TYPE_STRING); + + /* A tree store with some random words ... */ + gtk_tree_store_insert_with_values (store, &iter, NULL, 0, + 0, "File Manager", -1); + gtk_tree_store_insert_with_values (store, &iter, NULL, 0, + 0, "Gossip", -1); + gtk_tree_store_insert_with_values (store, &iter, NULL, 0, + 0, "System Settings", -1); + gtk_tree_store_insert_with_values (store, &iter, NULL, 0, + 0, "The GIMP", -1); + gtk_tree_store_insert_with_values (store, &iter, NULL, 0, + 0, "Terminal", -1); + gtk_tree_store_insert_with_values (store, &iter, NULL, 0, + 0, "Word Processor", -1); + + return GTK_TREE_MODEL (store); +} + +static void +selection_changed_cb (GtkTreeSelection *selection, + GtkWidget *tree_view) +{ + gtk_widget_trigger_tooltip_query (tree_view); +} + +static struct Rectangle +{ + gint x; + gint y; + gfloat r; + gfloat g; + gfloat b; + const char *tooltip; +} +rectangles[] = +{ + { 10, 10, 0.0, 0.0, 0.9, "Blue box!" }, + { 200, 170, 1.0, 0.0, 0.0, "Red thing" }, + { 100, 50, 0.8, 0.8, 0.0, "Yellow thing" } +}; + +static gboolean +query_tooltip_drawing_area_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer data) +{ + gint i; + + if (keyboard_tip) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS (rectangles); i++) + { + struct Rectangle *r = &rectangles[i]; + + if (r->x < x && x < r->x + 50 + && r->y < y && y < r->y + 50) + { + gtk_tooltip_set_markup (tooltip, r->tooltip); + return TRUE; + } + } + + return FALSE; +} + +static gboolean +drawing_area_expose (GtkWidget *drawing_area, + GdkEventExpose *event, + gpointer data) +{ + gint i; + cairo_t *cr; + + gdk_window_get_pointer (drawing_area->window, NULL, NULL, NULL); + + cr = gdk_cairo_create (drawing_area->window); + + cairo_rectangle (cr, 0, 0, + drawing_area->allocation.width, + drawing_area->allocation.height); + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_fill (cr); + + for (i = 0; i < G_N_ELEMENTS (rectangles); i++) + { + struct Rectangle *r = &rectangles[i]; + + cairo_rectangle (cr, r->x, r->y, 50, 50); + cairo_set_source_rgb (cr, r->r, r->g, r->b); + cairo_stroke (cr); + + cairo_rectangle (cr, r->x, r->y, 50, 50); + cairo_set_source_rgba (cr, r->r, r->g, r->b, 0.5); + cairo_fill (cr); + } + + cairo_destroy (cr); + + return FALSE; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *box; + GtkWidget *drawing_area; + GtkWidget *button; + + GtkWidget *tooltip_window; + GtkWidget *tooltip_button; + + GtkWidget *tree_view; + GtkTreeViewColumn *column; + + GtkWidget *text_view; + GtkTextBuffer *buffer; + GtkTextIter iter; + GtkTextTag *tag; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Tooltips test"); + gtk_container_set_border_width (GTK_CONTAINER (window), 10); + g_signal_connect (window, "delete_event", + G_CALLBACK (gtk_main_quit), NULL); + + box = gtk_vbox_new (FALSE, 3); + gtk_container_add (GTK_CONTAINER (window), box); + + /* A check button using the tooltip-markup property */ + button = gtk_check_button_new_with_label ("This one uses the tooltip-markup property"); + g_object_set (button, "tooltip-markup", "Hello, I am a static tooltip.", NULL); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + + /* A check button using the query-tooltip signal */ + button = gtk_check_button_new_with_label ("I use the query-tooltip signal"); + g_object_set (button, "has-tooltip", TRUE, NULL); + g_signal_connect (button, "query-tooltip", + G_CALLBACK (query_tooltip_cb), NULL); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + + /* A label */ + button = gtk_label_new ("I am just a label"); + gtk_label_set_selectable (GTK_LABEL (button), FALSE); + g_object_set (button, "tooltip-markup", "Label tooltip", NULL); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + + /* A selectable label */ + button = gtk_label_new ("I am a selectable label"); + gtk_label_set_selectable (GTK_LABEL (button), TRUE); + g_object_set (button, "tooltip-markup", "Another Label tooltip", NULL); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + + /* Another one, with a custom tooltip window */ + button = gtk_check_button_new_with_label ("This one has a custom tooltip window!"); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + + tooltip_window = gtk_window_new (GTK_WINDOW_POPUP); + tooltip_button = gtk_label_new ("blaat!"); + gtk_container_add (GTK_CONTAINER (tooltip_window), tooltip_button); + gtk_widget_show (tooltip_button); + + gtk_widget_set_tooltip_window (button, GTK_WINDOW (tooltip_window)); + g_signal_connect (button, "query-tooltip", + G_CALLBACK (query_tooltip_custom_cb), NULL); + g_object_set (button, "has-tooltip", TRUE, NULL); + + /* An insensitive button */ + button = gtk_button_new_with_label ("This one is insensitive"); + gtk_widget_set_sensitive (button, FALSE); + g_object_set (button, "tooltip-markup", "Insensitive!", NULL); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + + /* Testcases from Kris without a tree view don't exist. */ + tree_view = gtk_tree_view_new_with_model (create_model ()); + gtk_widget_set_size_request (tree_view, 200, 240); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree_view), + 0, "Test", + gtk_cell_renderer_text_new (), + "text", 0, + NULL); + + g_object_set (tree_view, "has-tooltip", TRUE, NULL); + g_signal_connect (tree_view, "query-tooltip", + G_CALLBACK (query_tooltip_tree_view_cb), NULL); + g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)), + "changed", G_CALLBACK (selection_changed_cb), tree_view); + + /* Set a tooltip on the column */ + column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree_view), 0); + gtk_tree_view_column_set_clickable (column, TRUE); + g_object_set (column->button, "tooltip-markup", "Header", NULL); + + gtk_box_pack_start (GTK_BOX (box), tree_view, FALSE, FALSE, 2); + + /* And a text view for Matthias */ + buffer = gtk_text_buffer_new (NULL); + + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, "Hello, the text ", -1); + + tag = gtk_text_buffer_create_tag (buffer, "bold", NULL); + g_object_set (tag, "weight", PANGO_WEIGHT_BOLD, NULL); + + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_buffer_insert_with_tags (buffer, &iter, "in bold", -1, tag, NULL); + + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, " has a tooltip!", -1); + + text_view = gtk_text_view_new_with_buffer (buffer); + gtk_widget_set_size_request (text_view, 200, 50); + + g_object_set (text_view, "has-tooltip", TRUE, NULL); + g_signal_connect (text_view, "query-tooltip", + G_CALLBACK (query_tooltip_text_view_cb), tag); + + gtk_box_pack_start (GTK_BOX (box), text_view, FALSE, FALSE, 2); + + /* Drawing area */ + drawing_area = gtk_drawing_area_new (); + gtk_widget_set_size_request (drawing_area, 320, 240); + g_object_set (drawing_area, "has-tooltip", TRUE, NULL); + g_signal_connect (drawing_area, "expose_event", + G_CALLBACK (drawing_area_expose), NULL); + g_signal_connect (drawing_area, "query-tooltip", + G_CALLBACK (query_tooltip_drawing_area_cb), NULL); + gtk_box_pack_start (GTK_BOX (box), drawing_area, FALSE, FALSE, 2); + + /* Done! */ + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +}