Re: Tooltips patch [take 1]



On Thu, 20 Jul 2006, Kristian Rietveld wrote:

Hey,


So I finally managed to get the event handling and all in the new tooltips code
right.  Attached is a patch with a first attempt at an implementation.  Of
course there's also some TODO items left:

 * The tooltip handling code in gtktooltip.c probably needs to be
   modified to be per display.

 * What to do with gtk_tooltip_force_query_tooltip() and
   gtk_tooltip_force_query_tooltip_for_widget().  The former will look up
   the widget under the mouse pointer and immediately display that
   tooltip.

that's not what was discussed/proposed though. force_query should simply
trigger the normal what-tooltip-is-needed-where logic that is also triggered
when the mouse moves. that's so that applications can notify gtk about the
need to display a (new) tooltip, but the usual tooltip behaviour like timeouts
is preserved.

i've been revisiting the proposal emails during this review and come to the
conclusion that "force_query" is probably a bit of a misnomer on my part.
what suits the intended purpose better is "trigger":

/* trigger tooltip logic when a widget is not known
 * (e.g. when chaging GtkSettings::display-tooltips=on/off)
 */
void gtk_tooltip_trigger_tooltip_query (GdkDisplay*);
/* could also be in the gtk_display_ namespace */

/* mostly for convenience:
 */
void gtk_widget_trigger_tooltip_query (GtkWidget *widget)
{
  gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (widget));
}
/* could also be in the gtk_tooltip_ namespace */

that should also make clear why gtk_display_trigger_tooltip_query() is a function that is conceived to be often needed by the gtktooltip.c implementation, but only rarely by the user (basically only when the mouse
stands still and the application changes tooltip relevant state).


   But I also see uses (and IIRC this has also been requested),
   to immediately show the tooltip of a specific widget.

i'm not sure what use case you're referring to, can you please elaborate?

   This probably
   also needs to display the tooltip above/below the widget and not
   the mouse pointer.  Once we decide on a set of functions we should add
   those to the header file.

i'd really like to nail the use cases here first, otherwise we'll end up
with some wild useless API growth once more. the former API discussions
tried to adress all use cases raised, either there's some misunderstanding
in how those are covered, or you're referring to cases not yet raised.
in either case, a bit of elaboration will help ;)

 * Also, do we want to be able to set a positioning function?

use cases? (again ;)

one related use case that comes to my mind here is the info window in the
maemo platform which is essentially used to display status information and
tooltips.
for that it's probably better to plug something even more generic like a
tooltips display function that can also adjust the tooltip's GtkWindow by
setting up styles, etc.


 * Some FIXMEs here and there: the positioning code needs work/polish,
   need to make the timeouts configurable, etc.

 * Right now the different things you can set on the GtkTooltip are
   packed in a box in this order: icon, markup, custom widget.  I guess
   people might want to change that order?  We could make it dependent on
   the order of the call to the _set functions, but I don't think I
   really like that idea :)

the original idea was to have a fixed order coded into gtk, and if that
isn't good enough for apps/widgets, they are up to use
gtk_widget_set_tooltip_window() (or gtk_tooltip_set_custom() and leave
icon/markup blank).


 * testtooltips.c still needs a license

heh, well, that's your's to pick ;)

most gtk+ tests are under LGPL, though i personally prefer something like:
  http://beast.gtk.org/mirror/LICENSE-AS-IS
if it comes to example code that people are likely to cut'n'paste into
their apps.

thanks,

-kris.



Index: tests/testtooltips.c
===================================================================
RCS file: tests/testtooltips.c
diff -N tests/testtooltips.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/testtooltips.c	20 Jul 2006 10:08:02 -0000

+static gboolean
+query_tooltip_custom_cb (GtkWidget  *widget,
+			 gint        x,
+			 gint        y,
+			 gboolean    keyboard_tip,
+			 GtkTooltip *tooltip,
+			 gpointer    data)
+{
+  GtkWindow *window = gtk_widget_get_tooltip_window (widget);
+
+  /* ... can manipulate window here ... */

/* ... should provide exemplary code of window modifications here ... */

;-)

+
+  return TRUE;
+}
+

Index: gtk/gtkwidget.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkwidget.h,v
retrieving revision 1.159
diff -u -p -r1.159 gtkwidget.h
--- gtk/gtkwidget.h	25 Apr 2006 14:27:31 -0000	1.159
+++ gtk/gtkwidget.h	20 Jul 2006 10:08:03 -0000
@@ -140,6 +140,8 @@ typedef struct _GtkWidgetClass	   GtkWid
 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);

@@ -408,9 +410,14 @@ struct _GtkWidgetClass
                                  GdkEventGrabBroken  *event);

   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);
@@ -773,6 +780,11 @@ void gtk_widget_add_mnemonic_label GtkWidget *label);
 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);
+

 GType           gtk_requisition_get_type (void) G_GNUC_CONST;
 GtkRequisition *gtk_requisition_copy     (const GtkRequisition *requisition);
Index: gtk/gtkwidget.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkwidget.c,v
retrieving revision 1.442
diff -u -p -r1.442 gtkwidget.c
--- gtk/gtkwidget.c	6 Jul 2006 05:14:03 -0000	1.442
+++ gtk/gtkwidget.c	20 Jul 2006 10:08:03 -0000
@@ -51,6 +51,7 @@
 #include "gtkintl.h"
 #include "gtkaccessible.h"
 #include "gtktooltips.h"
+#include "gtktooltip.h"
 #include "gtkinvisible.h"
 #include "gtkalias.h"

@@ -122,6 +123,7 @@ enum {
   CAN_ACTIVATE_ACCEL,
   GRAB_BROKEN,
   COMPOSITED_CHANGED,
+  QUERY_TOOLTIP,
   LAST_SIGNAL
 };

@@ -144,7 +146,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;
@@ -157,7 +161,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);
@@ -189,6 +192,11 @@ static void	gtk_widget_direction_changed
 						  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);

@@ -227,6 +235,9 @@ static GdkScreen *      gtk_widget_get_s
 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);

 static void gtk_widget_set_usize_internal (GtkWidget *widget,
 					   gint       width,
@@ -258,6 +269,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;

@@ -333,6 +347,9 @@ gtk_widget_class_init (GtkWidgetClass *k
   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);
@@ -404,6 +421,7 @@ gtk_widget_class_init (GtkWidgetClass *k
   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;

@@ -545,6 +563,21 @@ gtk_widget_class_init (GtkWidgetClass *k
  							 P_("Whether gtk_widget_show_all() should not affect this widget"),
  							 FALSE,
  							 GTK_PARAM_READWRITE));
+  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));
+  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),
@@ -1388,6 +1421,18 @@ gtk_widget_class_init (GtkWidgetClass *k
 		  _gtk_marshal_BOOLEAN__BOXED,
 		  G_TYPE_BOOLEAN, 1,
 		  GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+  widget_signals[QUERY_TOOLTIP] =
+    g_signal_new (I_("query_tooltip"),

nit pick, the canonical name here is 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
@@ -1668,7 +1713,10 @@ gtk_widget_set_property (GObject
   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));
@@ -1743,6 +1791,22 @@ gtk_widget_set_property (GObject 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));
+      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);
+
+      if (tooltip_markup)
+	g_free (tooltip_markup);
+      tooltip_markup = g_strdup (g_value_get_string (value));

there's g_value_dup_string();

+
+      g_object_set_qdata (object, quark_tooltip_markup, tooltip_markup);

here, you can simply use:
  g_object_set_qdata_full (object, quark_tooltip_markup, tooltip_markup, g_free);
which also introduces lesser chances of leaking.

+
+      tmp = (tooltip_window != NULL || (tooltip_markup ? strlen (tooltip_markup) : 0));
+      gtk_widget_set_has_tooltip (widget, tmp);
+      break;

you do realize though, that empty strings "" still get copied and allocate space,
allthough you regard them as has-tooltips=FALSE? ;)

     default:
       break;
     }
@@ -1759,6 +1823,7 @@ gtk_widget_get_property (GObject switch (prop_id)
     {
       gint *eventp;
+      gboolean *has_tooltip;
       GdkExtensionMode *modep;

     case PROP_NAME:
@@ -1837,6 +1902,16 @@ gtk_widget_get_property (GObject case PROP_NO_SHOW_ALL:
       g_value_set_boolean (value, gtk_widget_get_no_show_all (widget));
       break;
+    case PROP_HAS_TOOLTIP:
+      has_tooltip = g_object_get_qdata (object, quark_has_tooltip);
+      if (has_tooltip)
+	g_value_set_boolean (value, *has_tooltip);
+      else
+	g_value_set_boolean (value, FALSE);

urm, allocating memory for a boolean here is somewhat overkill, i'll
extend on that below though.

+      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;
@@ -2467,6 +2542,8 @@ gtk_widget_realize (GtkWidget *widget)

   if (!GTK_WIDGET_REALIZED (widget))
     {
+      gboolean *has_tooltip;
+
       /*
 	if (GTK_IS_CONTAINER (widget) && !GTK_WIDGET_NO_WINDOW (widget))
 	  g_message ("gtk_widget_realize(%s)", g_type_name (GTK_WIDGET_TYPE (widget)));
@@ -2484,6 +2561,22 @@ gtk_widget_realize (GtkWidget *widget)
       gtk_widget_ensure_style (widget);

       g_signal_emit (widget, widget_signals[REALIZE], 0);
+
+      has_tooltip = g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip);
+      if (has_tooltip && *has_tooltip == TRUE)
+        {
+	  if (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);
+	  else
+	    gtk_widget_add_events (widget,
+				   GDK_LEAVE_NOTIFY_MASK |
+				   GDK_POINTER_MOTION_MASK |
+				   GDK_POINTER_MOTION_HINT_MASK);
+      }

see my comment in gtk_widget_set_has_tooltip() on window events,
it applies here as well.
this can simpler be taken care off btw, by calling:
  gtk_widget_set_has_tooltip (widget, GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip)));
when gtk_widget_set_has_tooltip() either adjusts events unconditionally,
or by adding a 'force' argument to it that triggers it's event adjustment
logic regardless of the previous has_tooltip value.


       if (GTK_WIDGET_HAS_SHAPE_MASK (widget))
 	{
@@ -3658,6 +3751,8 @@ gtk_widget_real_focus_in_event (GtkWidge
 {
   gtk_widget_queue_shallow_draw (widget);

+  _gtk_tooltip_focus_in (widget);
+
   return FALSE;
 }

@@ -3667,6 +3762,8 @@ gtk_widget_real_focus_out_event (GtkWidg
 {
   gtk_widget_queue_shallow_draw (widget);

+  _gtk_tooltip_focus_out (widget);
+
   return FALSE;
 }

unfortunately most widgets override focus_in and focus_out without chaining,
so these calls need to be moved to gtk_widget_event_internal(), case GDK_FOCUS_CHANGE (yes, that's nasty).


@@ -4289,12 +4386,34 @@ gtk_widget_real_grab_focus (GtkWidget *f
 }

 static gboolean
+gtk_widget_real_query_tooltip (GtkWidget  *widget,
+			       gint        x,
+			       gint        y,
+			       gboolean    keyboard_tip,
+			       GtkTooltip *tooltip)
+{
+  gchar *tooltip_markup;
+
+  tooltip_markup = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_markup);
+
+  if (tooltip_markup)
+    {
+      gtk_tooltip_set_markup (tooltip, tooltip_markup);

this should only occour if GtkWidget::has-tooltip == TRUE.
so you can explicitely set:
  GtkWidget::tooltip-markup = "Some String";
  /* ... */
  GtkWidget::has-tooltip = FALSE; /* disable */
  /* ... */
  GtkWidget::has-tooltip = TRUE; /* re-enable */

+      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
@@ -6903,6 +7022,9 @@ gtk_widget_finalize (GObject *object)
   gint *events;
   GdkExtensionMode *mode;
   GtkAccessible *accessible;
+  gboolean *has_tooltip;
+  GtkWindow *tooltip_window;
+  gchar *tooltip_markup;

   gtk_grab_remove (widget);

@@ -6928,6 +7050,18 @@ gtk_widget_finalize (GObject *object)
   if (accessible)
     g_object_unref (accessible);

+  has_tooltip = g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip);
+  if (has_tooltip)
+    g_slice_free (gboolean, has_tooltip);
+
+  tooltip_window = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_window);
+  if (tooltip_window)
+    g_object_unref (tooltip_window);
+
+  tooltip_markup = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_markup);
+  if (tooltip_markup)
+    g_free (tooltip_markup);
+

this can all be gotten rid of by using the free handlers and cast macros
i described with your data_set function calls.

   G_OBJECT_CLASS (gtk_widget_parent_class)->finalize (object);
 }

@@ -8045,6 +8179,94 @@ gtk_widget_set_no_show_all (GtkWidget *w
     GTK_WIDGET_UNSET_FLAGS (widget, GTK_NO_SHOW_ALL);

   g_object_notify (G_OBJECT (widget), "no-show-all");
+}
+
+
+static void
+gtk_widget_set_has_tooltip (GtkWidget *widget,
+			    gboolean   has_tooltip)
+{
+  gboolean *priv_has_tooltip;
+
+  priv_has_tooltip = g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip);
+
+  if (!priv_has_tooltip)
+    {
+      priv_has_tooltip = g_slice_new (gboolean);
+      *priv_has_tooltip = !has_tooltip;

wow, this is using at least 8 bytes to store a single bit ;)
(regardless of using GSlice or malloc btw, and not counting the GData node)
you can simply use:
  gboolean has_tip = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip));
  g_object_set_qdata (G_OBJECT (widget), quark_has_tooltip, GUINT_TO_POINTER (has_tip));
which at least gets rid of the malloc/slice overhead.

+    }
+
+  if (*priv_has_tooltip != has_tooltip)
+    {
+      *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);
+

see the recent thread on:
  Subject: Re: gtk_widget_set_extension_events() is ignoring internal/input-only windows
this is probably not engough, we *might* need to walk the GdkWindow children hierarchy
of widget->window here, as long as child windows belong to widget, and extend their
set of events...

+	  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, priv_has_tooltip);
+    }
+}
+
+/**
+ * gtk_widget_set_tooltip_window:
+ *
+ *
+ * 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 (tooltip_window)
+    g_object_unref (tooltip_window);
+
+  if (custom_window)
+    g_object_ref (custom_window);

a nicer order here is:

+  if (custom_window)
+    g_object_ref (custom_window);
+  if (tooltip_window)
+    g_object_unref (tooltip_window);

that way, gtk_widget_set_tooltip_window() can be called twice in a row
with the same window.
also, you can use g_object_unref as destroy function on object data here,
so it gets auto-cleaned on finalize.

+
+  tooltip_window = custom_window;
+  g_object_set_qdata (G_OBJECT (widget), quark_tooltip_window, tooltip_window);
+
+  tmp = (tooltip_window != NULL || (tooltip_markup ? strlen (tooltip_markup) : 0));
+  gtk_widget_set_has_tooltip (widget, tmp);
+
+  /* FIXME: update current existing tooltip (if there is one) */

this is exactly what gtk_widget_trigger_tooltip_query() is intended for. ;)


+}
+
+/**
+ * gtk_widget_get_tooltip_window:
+ *
+ * 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);
 }

 #define __GTK_WIDGET_C__
Index: gtk/gtkmain.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmain.c,v
retrieving revision 1.279
diff -u -p -r1.279 gtkmain.c
--- gtk/gtkmain.c	21 Jun 2006 18:16:55 -0000	1.279
+++ gtk/gtkmain.c	20 Jul 2006 10:08:04 -0000
@@ -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"
@@ -1274,6 +1275,13 @@ gtk_main_do_event (GdkEvent *event)
     {
       _gtk_clipboard_handle_event (&event->owner_change);
       return;
+    }
+
+  if (event->type == GDK_LEAVE_NOTIFY
+      || event->type == GDK_MOTION_NOTIFY
+      || event->type == GDK_SCROLL)
+    {
+      _gtk_tooltip_handle_event (event);

hm, why exactly isn't GDK_ENTER_NOTIFY also interesting here?

also, note that you're trying to handle tooltiping *before* ordinary
event handling here.
i don't think that makes much sense, as handling motion/neter/leave
will often change app/widget/window state. that is especially true
for GDK_SCROLL, you'll allmost always query the wrong tooltip this
way.

instead, you should be calling gtk_tooltip_trigger_tooltip_query()
(or a GdkEvent* variant thereof), after the normal event handling
is done. (and be carefull there about event->any.window still
existing, currently, gtk_get_event_widget() does this for you).

     }

   /* Find the widget which got the event. We store the widget
Index: gtk/gtktooltip.h
===================================================================
RCS file: gtk/gtktooltip.h
diff -N gtk/gtktooltip.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gtk/gtktooltip.h	20 Jul 2006 10:08:04 -0000
@@ -0,0 +1,52 @@
+/* gtktooltip.h
+ *
+ * Copyright (C) 2006 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_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);
+
+G_END_DECLS
+
+#endif /* __GTK_TOOLTIP__ */
Index: gtk/gtktooltip.c
===================================================================
RCS file: gtk/gtktooltip.c
diff -N gtk/gtktooltip.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gtk/gtktooltip.c	20 Jul 2006 10:08:04 -0000
@@ -0,0 +1,734 @@
+/* gtktooltip.c
+ *
+ * Copyright (C) 2006 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"
+
+
+#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))
+#define GTK_TOOLTIP_GET_PRIVATE(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TOOLTIP, GtkTooltipPrivate))
+
+typedef struct _GtkTooltipClass   GtkTooltipClass;
+typedef struct _GtkTooltipPrivate GtkTooltipPrivate;
+
+struct _GtkTooltip
+{
+  GObject parent_instance;
+
+  /*< opaque >*/
+};

a cut-and-paste buglet? that comment is
probably not needed in a .c file ;)

+
+struct _GtkTooltipClass
+{
+  GObjectClass parent_class;
+};
+
+struct _GtkTooltipPrivate
+{
+  GtkWidget *window;
+  GtkWidget *alignment;
+  GtkWidget *box;
+  GtkWidget *image;
+  GtkWidget *label;
+  GtkWidget *custom_widget;
+
+  guint timeout_id;

this can be moved into struct _GtkTooltip if we decide to leave the
Tooltip class local to the .c file like you did it. which makes sense
i think, since there is no point in ever deriving from it or fiddling
with instances of that class.

+};
+
+
+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);
+
+  g_type_class_add_private (object_class, sizeof (GtkTooltipPrivate));
+}
+
+static gboolean
+gtk_tooltip_paint_window (GtkTooltip *tooltip)
+{
+  GtkRequisition req;
+  GtkTooltipPrivate *priv = GTK_TOOLTIP_GET_PRIVATE (tooltip);
+
+  gtk_widget_size_request (priv->window, &req);
+  gtk_paint_flat_box (priv->window->style,
+		      priv->window->window,
+		      GTK_STATE_NORMAL,
+		      GTK_SHADOW_OUT,
+		      NULL,
+		      priv->window,
+		      "tooltip",
+		      0, 0,
+		      req.width, req.height);
+
+  return FALSE;
+}
+
+static void
+gtk_tooltip_init (GtkTooltip *tooltip)
+{
+  GtkTooltipPrivate *priv = GTK_TOOLTIP_GET_PRIVATE (tooltip);
+
+  priv->timeout_id = 0;
+
+  priv->window = gtk_window_new (GTK_WINDOW_POPUP);

since we own the window object here, we should ref(window).
and since this window will end up on a specific screen/display, like you
already said in your comments, this will have to be done per display.

+  gtk_window_set_type_hint (GTK_WINDOW (priv->window),
+			    GDK_WINDOW_TYPE_HINT_TOOLTIP);
+  gtk_widget_set_app_paintable (priv->window, TRUE);
+  gtk_window_set_resizable (GTK_WINDOW (priv->window), FALSE);
+  gtk_widget_set_name (priv->window, "gtk-tooltips");

hm, this name is already in use by gtktooltips.c. it may be a good
idea to use "gtk-tooltip" here, especially if we introduce
new style properties for the new tooltips.

+
+  priv->alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+  gtk_alignment_set_padding (GTK_ALIGNMENT (priv->alignment),
+			     priv->window->style->ythickness,
+			     priv->window->style->ythickness,
+			     priv->window->style->xthickness,
+			     priv->window->style->xthickness);
+  gtk_container_add (GTK_CONTAINER (priv->window), priv->alignment);
+  gtk_widget_show (priv->alignment);
+
+  g_signal_connect_swapped (priv->window, "expose_event",
+			    G_CALLBACK (gtk_tooltip_paint_window), tooltip);
+
+  priv->box = gtk_hbox_new (FALSE, priv->window->style->xthickness);
+  gtk_container_add (GTK_CONTAINER (priv->alignment), priv->box);
+  gtk_widget_show (priv->box);
+
+  priv->image = gtk_image_new ();
+  gtk_box_pack_start (GTK_BOX (priv->box), priv->image,
+		      FALSE, FALSE, 0);
+
+  priv->label = gtk_label_new ("");
+  gtk_box_pack_start (GTK_BOX (priv->box), priv->label,
+		      FALSE, FALSE, 0);
+
+  priv->custom_widget = NULL;
+}
+
+
+
+void
+gtk_tooltip_set_markup (GtkTooltip  *tooltip,
+			const gchar *markup)
+{
+  GtkTooltipPrivate *priv;
+
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+
+  priv = GTK_TOOLTIP_GET_PRIVATE (tooltip);
+
+  gtk_label_set_markup (GTK_LABEL (priv->label), markup);
+
+  if (markup && strlen (markup) > 0)
+    gtk_widget_show (priv->label);
+  else
+    gtk_widget_hide (priv->label);
+}
+
+void
+gtk_tooltip_set_icon (GtkTooltip *tooltip,
+		      GdkPixbuf  *pixbuf)
+{
+  GtkTooltipPrivate *priv;
+
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+  if (pixbuf)
+    g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+  priv = GTK_TOOLTIP_GET_PRIVATE (tooltip);
+
+  gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
+
+  if (pixbuf)
+    gtk_widget_show (priv->image);
+  else
+    gtk_widget_hide (priv->image);
+}
+
+void
+gtk_tooltip_set_icon_from_stock (GtkTooltip  *tooltip,
+				 const gchar *stock_id,
+				 GtkIconSize  size)
+{
+  GtkTooltipPrivate *priv;
+
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+
+  priv = GTK_TOOLTIP_GET_PRIVATE (tooltip);
+
+  gtk_image_set_from_stock (GTK_IMAGE (priv->image), stock_id, size);
+
+  if (stock_id)
+    gtk_widget_show (priv->image);
+  else
+    gtk_widget_hide (priv->image);
+}
+
+void
+gtk_tooltip_set_custom (GtkTooltip *tooltip,
+			GtkWidget  *custom_widget)
+{
+  GtkTooltipPrivate *priv;
+
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+  g_return_if_fail (GTK_IS_WIDGET (custom_widget));
+
+  priv = GTK_TOOLTIP_GET_PRIVATE (tooltip);
+
+  if (priv->custom_widget)
+    gtk_container_remove (GTK_CONTAINER (priv->box), priv->custom_widget);
+
+  priv->custom_widget = custom_widget;
+
+  if (custom_widget)
+    {
+      gtk_container_add (GTK_CONTAINER (priv->box), custom_widget);
+      gtk_widget_show (custom_widget);
+    }

tooltip->custom definitely has to be ref-ed and unref-ed if you store its
handle away in a structure. also, it ideally should be removed from this
container as soon the tooltip window is hidden the next time.
(since after hiding the tip window, we will *always* requery before
showing again)

+}
+
+
+/**
+ * event handling, etc
+ */
+
+/* variables */
+static GtkWidget *current_tooltip_widget = NULL;
+static GtkWindow *current_window = NULL;
+static GtkTooltip *current_tooltip = NULL;

hm, this doesn't look all that good to me.
for one, tooltip state should be kept in GtkTooltip since it'll be needed
per display anyway, and for another, there's no need to keep the tooltip
widget handle around after completion of querying the tooltip before
showing it.

+static gboolean tooltip_visible = FALSE;
+static gboolean tooltip_browse_mode_enabled = FALSE;
+static gboolean tooltip_keyboard_mode_enabled = FALSE;
+static guint tooltip_timeout_id = 0;
+static guint tooltip_browse_mode_timeout_id = 0;
+
+
+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)
+    {
+      if (GTK_WIDGET_NO_WINDOW (child))
+        {
+	  if (child_loc->x >= child->allocation.x &&
+	      child_loc->y >= child->allocation.y &&
+	      child_loc->x < child->allocation.x + child->allocation.width &&
+	      child_loc->y < child->allocation.y + child->allocation.height)
+	    {
+	      child_loc->child = child;

hm, this looks like the proper condition to figure if an (x,y) pair
is inside a child, however it lacks coordinate translation.

+	    }
+	}
+      else
+        {
+	  gint x, y;
+
+	  gtk_widget_translate_coordinates (child_loc->container, child,
+					    child_loc->x, child_loc->y,
+					    &x, &y);
+
+	  if (x >= child_loc->container->allocation.x &&
+	      y >= child_loc->container->allocation.y &&
+	      x < child_loc->container->allocation.x + child_loc->container->allocation.width &&
+	      y < child_loc->container->allocation.y + child_loc->container->allocation.height)
+	    {
+	      child_loc->child = child;
+	      child_loc->x = x;
+	      child_loc->y = y;

this has the translation, but checks container coordinates instead
of child coordinates. i'll continue below...

+	    }
+	}
+    }
+}
+
+static GtkWidget *
+find_widget_under_pointer (GdkEvent *event,
+			   gint     *x,
+			   gint     *y)
+{
+  GtkWidget *event_widget;
+  struct ChildLocation child_loc = { NULL, NULL, 0, 0 };
+
+  switch (event->type)
+    {
+      case GDK_LEAVE_NOTIFY:
+	child_loc.x = event->crossing.x;
+	child_loc.y = event->crossing.y;
+	break;
+
+      case GDK_MOTION_NOTIFY:
+	child_loc.x = event->motion.x;
+	child_loc.y = event->motion.y;
+	break;
+
+      case GDK_SCROLL:
+	child_loc.x = event->scroll.x;
+	child_loc.y = event->scroll.y;
+	break;
+
+      default:
+	break;
+    }

hm, there's gdk_event_get_coords() btw.
but looking at the prototype and the way you call it,
it's probably easier to simply change the signature of
this function to take (GdkWindow *window, gint *x, gint *y),
you can take a look at gtk_get_event_widget() on how to get
from GdkWindow to the owning GtkWidget.

+
+  event_widget = gtk_get_event_widget (event);
+  if (GTK_IS_CONTAINER (event_widget))
+    {
+      GdkWindow *window;
+
+      child_loc.child = event_widget;
+
+      window = event->any.window;
+      while (window != child_loc.child->window)
+        {
+	  gint wx, wy;
+
+	  gdk_window_get_position (window, &wx, &wy);
+	  child_loc.x += wx;
+	  child_loc.y += wy;
+
+	  window = gdk_window_get_parent (window);
+
+	  if (!window)
+	    return NULL;
+	}

this if branch lacks a comment, it should have something like
/* translate event window coordinates into GtkContainer->window relative form */
if (GTK_IS_CONTAINER (event_widget))
{}
however, what you really want is GtkContainer relative coordinates
as you'll see in a second ;)

+
+      do
+        {
+	  event_widget = child_loc.container = child_loc.child;
+	  child_loc.child = NULL;
+
+	  gtk_container_foreach (GTK_CONTAINER (event_widget),
+				 child_location_foreach, &child_loc);
+	}
+      while (child_loc.child && GTK_IS_CONTAINER (child_loc.child));
+
+      if (child_loc.child)
+	event_widget = child_loc.child;
+      else if (child_loc.container)
+	event_widget = child_loc.container;
+    }

the whole widget/location finding logic you have here looks fragile
to me, both in terms of WINDOW vs. NO_WINDOW children and containers,
and in the presence of containers setting child windows. fortunately,
we can let gtk_widget_translate_coordinates() do 90% of the work for
us if used properly.
first, you need to get from event window relative coordinates to
container allocation relative, see gtk_widget_translate_coordinates's
/* Translate from window relative to allocation relative */ code
portion for this.
then, for each child (and you have to test children recursively, i.e.
also walk the children of the children you find in the foreach callback),
you call gtk_widget_translate_coordinates() to translate from container
relative coordnates to coordinates relative to child->allocation. that's
what you can use for inside tests then (and as already said, if (x,y) hit
the child's allocation you also need to foreach it's children if it is
a container).

+
+  if (x)
+    *x = child_loc.x;
+  if (y)
+    *y = child_loc.y;
+
+  return event_widget;
+}
+
+static gint
+tooltip_browse_mode_timeout (gpointer data)
+{
+  GDK_THREADS_ENTER ();
+
+  tooltip_browse_mode_enabled = FALSE;
+  tooltip_browse_mode_timeout_id = 0;
+
+  GDK_THREADS_LEAVE ();
+
+  return FALSE;
+}
+
+static void
+gtk_tooltip_show_tooltip (void)
+{
+  gint x, y;
+  gint w, h;
+  gint monitor_num, px, py;
+  GdkScreen *screen;
+  GdkScreen *pointer_screen;
+  GdkRectangle monitor;
+
+  GtkWidget *widget = current_tooltip_widget;

there should be no need here to refer to current_tooltip_widget.
either the tip window is readily setup (including its contents *and*
position) and can be shown, or not.

hm, looking at the event handling below, you seem to be using the static
variables because you have a somewhat unfortunate split in your logic.
i.e. you always query tooltips on every mouse motion, but only show
the tip once the delay expires. it'll be much more efficient and simpler
in terms of code logic if you:
- when an event arrives (movement, focus-out), figure its display and the
  display's tooltip object. then popdown the tooltip.
- when an event arrives (movement or trigger_tooltip_query), figure
  GdkWindow,x,y, get the tooltip object for this display, restart the
  tooltip object's timeout.
- once the timeout expired, figure the topmost child widget that x,y
  points at (by recursively walking container->children),
  and query it for a tooltip.
- if the topmost child doesn't have a tooltip, continue querying its
  parents until a widget with a tooltip is found, by walking child->parent.
  this is part of the initial proposal but missing in your implementation.
- show the tip window with the queried contents right away.

+
+  /* Position the tooltip */
+  if (tooltip_keyboard_mode_enabled)
+    {
+      gdk_window_get_origin (widget->window, &x, &y);
+      if (GTK_WIDGET_NO_WINDOW (widget))
+        {
+	  x += widget->allocation.x;
+	  y += widget->allocation.y;
+	}
+
+      /* FIXME: want to get this right */
+      x += widget->allocation.width / 2;
+      y += widget->allocation.height + 4;

ok, fix it then ;)
do you have any questions regarding the positioning?

+    }
+  else
+    {
+      gdk_window_get_pointer (gdk_screen_get_root_window (gtk_widget_get_screen (widget)), &x, &y, NULL);
+
+      /* FIXME: take RTL into account */
+      /* FIXME: maybe take size of cursor into account */
+      x += 8;
+      y += 8;
+    }
+
+  if (current_window)
+    {
+      GtkRequisition requisition;
+
+      gtk_widget_size_request (GTK_WIDGET (current_window), &requisition);
+      w = requisition.width;
+      h = requisition.height;
+    }
+
+  screen = gtk_widget_get_screen (widget);
+
+  gdk_display_get_pointer (gdk_screen_get_display (screen),
+			   &pointer_screen, &px, &py, NULL);
+  if (pointer_screen != screen)
+    {
+      px = x;
+      py = y;
+    }
+  monitor_num = gdk_screen_get_monitor_at_point (screen, px, py);
+  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+  if (x > monitor.x + monitor.width)
+    x -= x - (monitor.x + monitor.width + w);
+  else if (x < monitor.x)
+    x = monitor.x;
+
+  if (y > monitor.y + monitor.height)
+    y -= y - (monitor.y + monitor.height + h);
+
+  /* Show it */
+  if (current_window)
+    {
+      gtk_window_move (GTK_WINDOW (current_window), x, y);
+      gtk_widget_show (GTK_WIDGET (current_window));
+    }
+
+  tooltip_visible = TRUE;
+}
+
+static void
+gtk_tooltip_hide_tooltip (GtkWidget *widget)
+{
+  if (tooltip_timeout_id)
+    {
+      g_source_remove (tooltip_timeout_id);
+      tooltip_timeout_id = 0;
+    }
+
+  /* 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 (500,
+						    tooltip_browse_mode_timeout,
+						    NULL);
+
+  if (current_window)
+    gtk_widget_hide (GTK_WIDGET (current_window));

here or in a ::hide callback, you should actually cleanup ->custom.

+
+  tooltip_visible = FALSE;
+}
+
+static gint
+tooltip_timeout (gpointer data)
+{
+  GDK_THREADS_ENTER ();
+
+  gtk_tooltip_show_tooltip ();
+  tooltip_timeout_id = 0;
+
+  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;
+    }
+
+  GDK_THREADS_LEAVE ();
+
+  return FALSE;
+}
+
+static void
+gtk_tooltip_start_delay (GtkWidget *widget)
+{
+  guint timeout;
+
+  if (tooltip_visible || tooltip_timeout_id)
+    return;
+
+  /* FIXME: make this configurable */
+  if (tooltip_browse_mode_enabled)
+    timeout = 100;
+  else
+    timeout = 1500;
+
+  tooltip_timeout_id = g_timeout_add (timeout,
+				      tooltip_timeout,
+				      widget);
+}
+
+void
+_gtk_tooltip_focus_in (GtkWidget *widget)
+{
+  gint x, y;
+  gboolean has_tooltip;
+  gboolean return_value;
+
+  g_object_get (widget, "has-tooltip", &has_tooltip, NULL);
+
+  if (!tooltip_keyboard_mode_enabled || !has_tooltip)
+    return;
+
+  gdk_window_get_pointer (widget->window, &x, &y, NULL);
+
+  if (!current_window)
+    {
+      current_window = gtk_widget_get_tooltip_window (widget);
+      if (!current_window)
+        {
+	  current_tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
+	  current_window = GTK_WINDOW (GTK_TOOLTIP_GET_PRIVATE (current_tooltip)->window);
+	}
+      current_tooltip_widget = widget;
+    }
+

you should only emit query-tooltip on
widgets that have ::has-tooltip==TRUE.
(and continue on widget->parent while !return_value)


+  g_signal_emit_by_name (widget, "query-tooltip",
+			 x, y,
+			 TRUE,
+			 current_tooltip,
+			 &return_value);
+
+  if (!return_value)
+    gtk_tooltip_hide_tooltip (widget);
+  else
+    gtk_tooltip_show_tooltip ();
+}
+
+void
+_gtk_tooltip_focus_out (GtkWidget *widget)
+{
+  gboolean has_tooltip;
+
+  g_object_get (widget, "has-tooltip", &has_tooltip, NULL);
+
+  if (!tooltip_keyboard_mode_enabled || !has_tooltip
+      || current_tooltip_widget != widget)
+    return;
+
+  /* Hide tooltip */
+  gtk_tooltip_hide_tooltip (widget);
+
+  if (current_tooltip)
+    g_object_unref (current_tooltip);
+  current_tooltip = NULL;
+  current_window = NULL;
+  current_tooltip_widget = NULL;
+}
+
+void
+_gtk_tooltip_toggle_keyboard_mode (GtkWidget *widget)
+{
+  tooltip_keyboard_mode_enabled ^= 1;
+
+  if (tooltip_keyboard_mode_enabled)
+    _gtk_tooltip_focus_in (widget);
+  else
+    {
+      gtk_tooltip_hide_tooltip (current_tooltip_widget);
+
+      if (current_tooltip)
+	g_object_unref (current_tooltip);
+      current_tooltip = NULL;
+      current_window = NULL;
+      current_tooltip_widget = NULL;
+    }
+}
+
+void
+_gtk_tooltip_handle_event (GdkEvent *event)
+{
+  gint x, y;
+  gboolean has_tooltip = FALSE;
+  gboolean return_value;
+  GtkWidget *tooltip_widget;
+  GtkWidget *old_widget;
+

apart from the general even handling comments from above, there's also a bunch
of events that you want to catch in gtkmain.c before they are dispatched, and
popdown a current tooltip in response. e.g. key presses, buttons, grab_broken,
drag events, etc.

+  if (tooltip_keyboard_mode_enabled)
+    return;
+
+  old_widget = tooltip_widget = find_widget_under_pointer (event, &x, &y);
+  if (!tooltip_widget)
+    return;
+
+  g_object_get (tooltip_widget, "has-tooltip", &has_tooltip, NULL);
+
+  if (event->type == GDK_MOTION_NOTIFY && event->motion.is_hint)
+    {
+      gdk_window_get_pointer (tooltip_widget->window, NULL, NULL, NULL);
+    }
+
+  /* We now have the toplevel widget under the mouse pointer, now find the
+   * widget with the tooltip in the stack
+   */
+  while (tooltip_widget && !has_tooltip)
+    {
+      tooltip_widget = tooltip_widget->parent;
+      if (tooltip_widget)
+	g_object_get (tooltip_widget, "has-tooltip", &has_tooltip, NULL);
+    }
+
+  if (current_tooltip_widget && current_tooltip_widget != tooltip_widget)
+    {
+      gtk_tooltip_hide_tooltip (tooltip_widget);
+      if (current_tooltip)
+	g_object_unref (current_tooltip);
+      current_tooltip = NULL;
+      current_window = NULL;
+      current_tooltip_widget = NULL;
+    }
+
+  if (!tooltip_widget || !has_tooltip)
+    return;
+
+  /* Handle tooltip event for this widget */
+#if 0
+  g_print ("%p (%s)  has tooltip=%d\n",
+	   tooltip_widget, gtk_widget_get_name (tooltip_widget), has_tooltip);
+#endif
+
+  switch (event->type)
+    {
+      case GDK_LEAVE_NOTIFY:
+	gtk_tooltip_hide_tooltip (tooltip_widget);
+	if (current_tooltip)
+	  g_object_unref (current_tooltip);
+	current_tooltip = NULL;
+	current_window = NULL;
+	current_tooltip_widget = NULL;
+	break;
+
+      case GDK_SCROLL:
+      case GDK_MOTION_NOTIFY:
+	if (!current_window)
+	  {
+	    current_window = gtk_widget_get_tooltip_window (tooltip_widget);
+	    if (!current_window)
+	      {
+		current_tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
+		current_window = GTK_WINDOW (GTK_TOOLTIP_GET_PRIVATE (current_tooltip)->window);
+	      }
+	    current_tooltip_widget = tooltip_widget;
+	  }
+
+	g_signal_emit_by_name (tooltip_widget, "query-tooltip",
+			       x, y,
+			       FALSE,
+			       current_tooltip,
+			       &return_value);
+
+	if (!return_value)
+	  gtk_tooltip_hide_tooltip (tooltip_widget);
+	else
+	  gtk_tooltip_start_delay (tooltip_widget);
+	break;
+
+      default:
+	break;
+    }
+}
+
+void
+gtk_tooltip_force_query_tooltip_for_widget (GtkWidget *widget)
+{
+  gint x, y;
+  gboolean return_value;
+
+  if (!current_window)
+    {
+      current_window = gtk_widget_get_tooltip_window (widget);
+      if (!current_window)
+        {
+	  current_tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
+	  current_window = GTK_WINDOW (GTK_TOOLTIP_GET_PRIVATE (current_tooltip)->window);
+	}
+      current_tooltip_widget = widget;
+    }
+
+  g_signal_emit_by_name (widget, "query-tooltip",
+			 x, y,
+			 tooltip_keyboard_mode_enabled,
+			 current_tooltip,
+			 &return_value);
+
+  if (!return_value)
+    gtk_tooltip_hide_tooltip (widget);
+  else
+    gtk_tooltip_show_tooltip ();
+}
+
+void
+gtk_tooltip_force_query_tooltip (GdkDisplay *display)
+{
+  gint x, y;
+  GdkWindow *window;
+  GdkEvent event;
+  GtkWidget *widget;
+
+  if (!display)
+    display = gdk_screen_get_display (gdk_screen_get_default ());
+
+  window = gdk_display_get_window_at_pointer (display, &x, &y);
+
+  if (!window)
+    return;
+
+  /* evil */
+  event.type = GDK_MOTION_NOTIFY;
+  event.any.window = window;
+
+  widget = find_widget_under_pointer (&event, &x, &y);
+
+  gtk_tooltip_force_query_tooltip_for_widget (widget);
+}

how the remainig uncommented functions need to be fixed/changed should
be clear from my earlier comments and to a good part also from your
own FIXMEs ;)

all in all, this looks like a substantial prototype implementation
already, but it's certainly still lacking in some key areas.
in any case, it's so large already, that patch+review is becoming
tedious as development process, therefore i think we should get this
into CVS in its current form and build/extend/fix from there.
a branch might be good for that but it wouldn't be too bad having it
in HEAD already either.
that'll also allow concurrent hacking on the new tooltip implementation.

---
ciaoTJ



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]