This adds support for dynamic, context-sensitive tooltips in Swing. This
patch is accompanied by a regression test in Mauve.

2006-10-14  Roman Kennke  <[EMAIL PROTECTED]>

        PR 27957
        * javax/swing/JComponent.java
        (toolTipText): Removed field.
        (createToolTip): Don't set tooltip text here. This is done
        in the ToolTipManager.
        (setToolTipText): Set tooltip text as client property.
        (getToolTipText): Get tooltip text from client property.
        * javax/swing/ToolTipManager.java
        (currentComponent): Made field non-static and of type JComponent.
        (currentPoint): Made field non-static.
        (currentTip): Made field non-static.
        (popup): Made field non-static.
        (toolTipText): New field. Stores the current tooltip text.
        (checkTipUpdate): New helper method. Checks for updates of
        the tooltip text and triggers the appropriate actions.
        (getContentPaneDeepestComponent): Removed unneeded casts.
        (mouseEntered): Removed unneeded cast. Initially fetch tooltip
        text from component.
        (mouseMoved): Check for tooltip text updates.
        (showTip): Set tooltip text from current setting.

/Roman

Index: javax/swing/JComponent.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/JComponent.java,v
retrieving revision 1.146
diff -u -1 -5 -r1.146 JComponent.java
--- javax/swing/JComponent.java	12 Oct 2006 10:20:52 -0000	1.146
+++ javax/swing/JComponent.java	14 Oct 2006 13:22:54 -0000
@@ -529,38 +529,30 @@
    *
    * @see #getAlignmentY
    * @see #setAlignmentY
    * @see javax.swing.OverlayLayout
    * @see javax.swing.BoxLayout
    */
   float alignmentY = -1.0F;
 
   /** 
    * The border painted around this component.
    * 
    * @see #paintBorder
    */
   Border border;
 
-  /** 
-   * The text to show in the tooltip associated with this component.
-   * 
-   * @see #setToolTipText
-   * @see #getToolTipText()
-   */
-   String toolTipText;
-
   /**
    * The popup menu for the component.
    * 
    * @see #getComponentPopupMenu()
    * @see #setComponentPopupMenu(JPopupMenu)
    */
   JPopupMenu componentPopupMenu;
    
   /**
    * A flag that controls whether the [EMAIL PROTECTED] #getComponentPopupMenu()} method
    * looks to the component's parent when the <code>componentPopupMenu</code>
    * field is <code>null</code>.
    */
   boolean inheritsPopupMenu;
   
@@ -1427,97 +1419,98 @@
       }
   }
 
   /**
    * Return the <code>toolTip</code> property of this component, creating it and
    * setting it if it is currently <code>null</code>. This method can be
    * overridden in subclasses which wish to control the exact form of
    * tooltip created.
    *
    * @return The current toolTip
    */
   public JToolTip createToolTip()
   {
     JToolTip toolTip = new JToolTip();
     toolTip.setComponent(this);
-    toolTip.setTipText(toolTipText);
-
     return toolTip;
   }
 
   /**
-   * Return the location at which the [EMAIL PROTECTED] #toolTipText} property should be
-   * displayed, when triggered by a particular mouse event. 
+   * Return the location at which the <code>toolTipText</code> property should
+   * be displayed, when triggered by a particular mouse event. 
    *
    * @param event The event the tooltip is being presented in response to
    *
    * @return The point at which to display a tooltip, or <code>null</code>
    *     if swing is to choose a default location.
    */
   public Point getToolTipLocation(MouseEvent event)
   {
     return null;
   }
 
   /**
-   * Set the value of the [EMAIL PROTECTED] #toolTipText} property.
+   * Set the tooltip text for this component. If a non-<code>null</code>
+   * value is set, this component is registered in the
+   * <code>ToolTipManager</code> in order to turn on tooltips for this
+   * component. If a <code>null</code> value is set, tooltips are turne off
+   * for this component.
    *
-   * @param text The new property value
+   * @param text the tooltip text for this component
    *
    * @see #getToolTipText()
+   * @see #getToolTipText(MouseEvent)
    */
   public void setToolTipText(String text)
   {
+    String old = getToolTipText();
+    putClientProperty(TOOL_TIP_TEXT_KEY, text);
+    ToolTipManager ttm = ToolTipManager.sharedInstance();
     if (text == null)
-    {
-      ToolTipManager.sharedInstance().unregisterComponent(this);
-      toolTipText = null;
-      return;
-    }
-
-    // XXX: The tip text doesn't get updated unless you set it to null
-    // and then to something not-null. This is consistent with the behaviour
-    // of Sun's ToolTipManager.
-
-    String oldText = toolTipText;
-    toolTipText = text;
-
-    if (oldText == null)
-      ToolTipManager.sharedInstance().registerComponent(this);
+      ttm.unregisterComponent(this);
+    else if (old == null)
+      ttm.registerComponent(this);
   }
 
   /**
-   * Get the value of the [EMAIL PROTECTED] #toolTipText} property.
+   * Returns the current tooltip text for this component, or <code>null</code>
+   * if none has been set.
    *
-   * @return The current property value
+   * @return the current tooltip text for this component, or <code>null</code>
+   *         if none has been set
    *
    * @see #setToolTipText
+   * @see #getToolTipText(MouseEvent)
    */
   public String getToolTipText()
   {
-    return toolTipText;
+    return (String) getClientProperty(TOOL_TIP_TEXT_KEY);
   }
 
   /**
-   * Get the value of the [EMAIL PROTECTED] #toolTipText} property, in response to a
-   * particular mouse event.
+   * Returns the tooltip text for this component for a particular mouse
+   * event. This can be used to support context sensitive tooltips that can
+   * change with the mouse location. By default this returns the static
+   * tooltip text returned by [EMAIL PROTECTED] #getToolTipText()}.
    *
-   * @param event The mouse event which triggered the tooltip
+   * @param event the mouse event which triggered the tooltip
    *
-   * @return The current property value
+   * @return the tooltip text for this component for a particular mouse
+   *         event
    *
    * @see #setToolTipText
+   * @see #getToolTipText()
    */
   public String getToolTipText(MouseEvent event)
   {
     return getToolTipText();
   }
   
   /**
    * Returns the flag that controls whether or not the component inherits its
    * parent's popup menu when no popup menu is specified for this component.
    * 
    * @return A boolean.
    * 
    * @since 1.5
    * 
    * @see #setInheritsPopupMenu(boolean)
Index: javax/swing/ToolTipManager.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/ToolTipManager.java,v
retrieving revision 1.33
diff -u -1 -5 -r1.33 ToolTipManager.java
--- javax/swing/ToolTipManager.java	9 Jul 2006 20:57:45 -0000	1.33
+++ javax/swing/ToolTipManager.java	14 Oct 2006 13:22:54 -0000
@@ -151,40 +151,45 @@
   Timer insideTimer;
 
   /** A global enabled setting for the ToolTipManager. */
   private transient boolean enabled = true;
 
   /** lightWeightPopupEnabled */
   protected boolean lightWeightPopupEnabled = true;
 
   /** heavyWeightPopupEnabled */
   protected boolean heavyWeightPopupEnabled = false;
 
   /** The shared instance of the ToolTipManager. */
   private static ToolTipManager shared;
 
   /** The current component the tooltip is being displayed for. */
-  private static Component currentComponent;
+  private JComponent currentComponent;
 
   /** The current tooltip. */
-  private static JToolTip currentTip;
+  private JToolTip currentTip;
+
+  /**
+   * The tooltip text.
+   */
+  private String toolTipText;
 
   /** The last known position of the mouse cursor. */
-  private static Point currentPoint;
-  
+  private Point currentPoint;
+
   /**  */
-  private static Popup popup;
+  private Popup popup;
 
   /**
    * Creates a new ToolTipManager and sets up the timers.
    */
   ToolTipManager()
   {
     enterTimer = new Timer(750, new insideTimerAction());
     enterTimer.setRepeats(false);
 
     insideTimer = new Timer(4000, new stillInsideTimerAction());
     insideTimer.setRepeats(false);
 
     exitTimer = new Timer(500, new outsideTimerAction());
     exitTimer.setRepeats(false);
   }
@@ -352,32 +357,32 @@
    * This method is called whenever the mouse enters a JComponent registered
    * with the ToolTipManager. When the mouse enters within the period of time
    * specified by the reshow delay, the tooltip will be displayed
    * immediately. Otherwise, it must wait for the initial delay before
    * displaying the tooltip.
    *
    * @param event The MouseEvent.
    */
   public void mouseEntered(MouseEvent event)
   {
     if (currentComponent != null
         && getContentPaneDeepestComponent(event) == currentComponent)
       return;
     currentPoint = event.getPoint();
 
-    currentComponent = (Component) event.getSource();
-
+    currentComponent = (JComponent) event.getSource();
+    toolTipText = currentComponent.getToolTipText(event);
     if (exitTimer.isRunning())
       {
         exitTimer.stop();
         showTip();
         return;
       }
     // This should always be stopped unless we have just fake-exited.
     if (!enterTimer.isRunning())
       enterTimer.start();
   }
 
   /**
    * This method is called when the mouse exits a JComponent registered with the
    * ToolTipManager. When the mouse exits, the tooltip should be hidden
    * immediately.
@@ -431,51 +436,95 @@
   {
     currentPoint = event.getPoint();
     if (enterTimer.isRunning())
       enterTimer.restart();
   }
 
   /**
    * This method is called when the mouse is moved in a JComponent registered
    * with the ToolTipManager.
    *
    * @param event The MouseEvent.
    */
   public void mouseMoved(MouseEvent event)
   {
     currentPoint = event.getPoint();
-    if (enterTimer.isRunning())
-      enterTimer.restart(); 
+    if (currentTip != null && currentTip.isShowing())
+      checkTipUpdate(event);
+    else
+      {
+        if (enterTimer.isRunning())
+          enterTimer.restart();
+      }
+  }
+
+  /**
+   * Checks if the tooltip's text or location changes when the mouse is moved
+   * over the component.
+   */
+  private void checkTipUpdate(MouseEvent ev)
+  {
+    JComponent comp = (JComponent) ev.getSource();
+    String newText = comp.getToolTipText(ev);
+    String oldText = toolTipText;
+    if (newText != null)
+      {
+        if (((newText != null && newText.equals(oldText)) || newText == null))
+          {
+            // No change at all. Restart timers.
+            if (popup == null)
+              enterTimer.restart();
+            else
+              insideTimer.restart();
+          }
+        else
+          {
+            // Update the tooltip.
+            toolTipText = newText;
+            hideTip();
+            showTip();
+            exitTimer.stop();
+          }
+      }
+    else
+      {
+        // Hide tooltip.
+        currentTip = null;
+        currentPoint = null;
+        hideTip();
+        enterTimer.stop();
+        exitTimer.stop();
+      }
   }
 
   /**
    * This method displays the ToolTip. It can figure out the method needed to
    * show it as well (whether to display it in heavyweight/lightweight panel
    * or a window.)  This is package-private to avoid an accessor method.
    */
   void showTip()
   {
     if (!enabled || currentComponent == null || !currentComponent.isEnabled()
         || !currentComponent.isShowing())
       {
         popup = null;
         return;
       }
 
-    if (currentTip == null || currentTip.getComponent() != currentComponent
-        && currentComponent instanceof JComponent)
-      currentTip = ((JComponent) currentComponent).createToolTip();
+    if (currentTip == null || currentTip.getComponent() != currentComponent)
+      currentTip = currentComponent.createToolTip();
+    currentTip.setTipText(toolTipText);
 
     Point p = currentPoint;
     Point cP = currentComponent.getLocationOnScreen();
     Dimension dims = currentTip.getPreferredSize();
     
     JLayeredPane pane = null;
     JRootPane r = ((JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
                                                                  currentComponent));
     if (r != null)
       pane = r.getLayeredPane();
     if (pane == null)
       return;
     
     p.translate(cP.x, cP.y);
     adjustLocation(p, pane, dims);
@@ -519,26 +568,26 @@
       popup.hide();
   }
 
   /**
    * This method returns the deepest component in the content pane for the
    * first RootPaneContainer up from the currentComponent. This method is
    * used in conjunction with one of the mouseXXX methods.
    *
    * @param e The MouseEvent.
    *
    * @return The deepest component in the content pane.
    */
   private Component getContentPaneDeepestComponent(MouseEvent e)
   {
     Component source = (Component) e.getSource();
-    Container parent = (Container) SwingUtilities.getAncestorOfClass(JRootPane.class,
-                                                                     currentComponent);
+    Container parent = SwingUtilities.getAncestorOfClass(JRootPane.class,
+                                                         currentComponent);
     if (parent == null)
       return null;
     parent = ((JRootPane) parent).getContentPane();
     Point p = e.getPoint();
     p = SwingUtilities.convertPoint(source, p, parent);
     Component target = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y);
     return target;
   }
 }

Reply via email to