summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog30
-rw-r--r--gtk/Makefile.am2
-rw-r--r--gtk/gtk.h1
-rw-r--r--gtk/gtk.symbols14
-rw-r--r--gtk/gtkmain.c15
-rw-r--r--gtk/gtkmarshalers.list1
-rw-r--r--gtk/gtkrc.c2
-rw-r--r--gtk/gtksettings.c70
-rw-r--r--gtk/gtktooltip.c1064
-rw-r--r--gtk/gtktooltip.h55
-rw-r--r--gtk/gtkwidget.c280
-rw-r--r--gtk/gtkwidget.h15
-rw-r--r--gtk/gtkwindow.h1
-rw-r--r--tests/Makefile.am8
-rw-r--r--tests/testtooltips.c377
15 files changed, 1928 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index ee01fe0424..0ff9c9aadf 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2007-02-06 Kristian Rietveld <kris@imendio.com>
+
+ 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 <domlachowicz@gmail.com>
* 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 <gtk/gtktoolbar.h>
#include <gtk/gtktoolbutton.h>
#include <gtk/gtktoolitem.h>
+#include <gtk/gtktooltip.h>
#include <gtk/gtktooltips.h>
#include <gtk/gtktree.h>
#include <gtk/gtktreednd.h>
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 \"*<GtkMenuItem>*\" style : gtk \"gtk-default-menu-item-style\"\n"
"widget_class \"*<GtkMenuBar>*<GtkMenuItem>\" 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,
@@ -557,6 +560,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:
*
* When %TRUE, keyboard navigation should be able to reach all widgets
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 <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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#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 <string.h>
+
+
+#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 <link linkend="PangoMarkupFormat">Pango markup format</link>) or %NULL
+ *
+ * Sets the text of the tooltip to be @str, which is marked up
+ * with the <link
+ * linkend="PangoMarkupFormat">Pango text markup language</link>.
+ * 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,
+ &current_tooltip->last_x,
+ &current_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,
+ &current_tooltip->last_x,
+ &current_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 <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, 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 <link linkend="PangoMarkupFormat">Pango text markup language</link>.
+ * 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;
@@ -4355,12 +4487,36 @@ 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)
{
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 <kris@imendio.com>
+ *
+ * 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 <gtk/gtk.h>
+
+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, "<b>Path %s:</b> %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;
+}