1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 
  27 package javax.swing;
  28 
  29 import java.awt.event.*;
  30 import java.awt.*;
  31 import java.util.Objects;
  32 
  33 /**
  34  * Manages all the <code>ToolTips</code> in the system.
  35  * <p>
  36  * ToolTipManager contains numerous properties for configuring how long it
  37  * will take for the tooltips to become visible, and how long till they
  38  * hide. Consider a component that has a different tooltip based on where
  39  * the mouse is, such as JTree. When the mouse moves into the JTree and
  40  * over a region that has a valid tooltip, the tooltip will become
  41  * visible after <code>initialDelay</code> milliseconds. After
  42  * <code>dismissDelay</code> milliseconds the tooltip will be hidden. If
  43  * the mouse is over a region that has a valid tooltip, and the tooltip
  44  * is currently visible, when the mouse moves to a region that doesn't have
  45  * a valid tooltip the tooltip will be hidden. If the mouse then moves back
  46  * into a region that has a valid tooltip within <code>reshowDelay</code>
  47  * milliseconds, the tooltip will immediately be shown, otherwise the
  48  * tooltip will be shown again after <code>initialDelay</code> milliseconds.
  49  *
  50  * @see JComponent#createToolTip
  51  * @author Dave Moore
  52  * @author Rich Schiavi
  53  * @since 1.2
  54  */
  55 public class ToolTipManager extends MouseAdapter implements MouseMotionListener  {
  56     Timer enterTimer, exitTimer, insideTimer;
  57     String toolTipText;
  58     Point  preferredLocation;
  59     JComponent insideComponent;
  60     MouseEvent mouseEvent;
  61     boolean showImmediately;
  62     private static final Object TOOL_TIP_MANAGER_KEY = new Object();
  63     transient Popup tipWindow;
  64     /** The Window tip is being displayed in. This will be non-null if
  65      * the Window tip is in differs from that of insideComponent's Window.
  66      */
  67     private Window window;
  68     JToolTip tip;
  69 
  70     private Rectangle popupRect = null;
  71     private Rectangle popupFrameRect = null;
  72 
  73     boolean enabled = true;
  74     private boolean tipShowing = false;
  75 
  76     private FocusListener focusChangeListener = null;
  77     private MouseMotionListener moveBeforeEnterListener = null;
  78     private KeyListener accessibilityKeyListener = null;
  79 
  80     private KeyStroke postTip;
  81     private KeyStroke hideTip;
  82 
  83     // PENDING(ges)
  84     protected boolean lightWeightPopupEnabled = true;
  85     protected boolean heavyWeightPopupEnabled = false;
  86 
  87     ToolTipManager() {
  88         enterTimer = new Timer(750, new insideTimerAction());
  89         enterTimer.setRepeats(false);
  90         exitTimer = new Timer(500, new outsideTimerAction());
  91         exitTimer.setRepeats(false);
  92         insideTimer = new Timer(4000, new stillInsideTimerAction());
  93         insideTimer.setRepeats(false);
  94 
  95         moveBeforeEnterListener = new MoveBeforeEnterListener();
  96         accessibilityKeyListener = new AccessibilityKeyListener();
  97 
  98         postTip = KeyStroke.getKeyStroke(KeyEvent.VK_F1, InputEvent.CTRL_MASK);
  99         hideTip =  KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
 100     }
 101 
 102     /**
 103      * Enables or disables the tooltip.
 104      *
 105      * @param flag  true to enable the tip, false otherwise
 106      */
 107     public void setEnabled(boolean flag) {
 108         enabled = flag;
 109         if (!flag) {
 110             hideTipWindow();
 111         }
 112     }
 113 
 114     /**
 115      * Returns true if this object is enabled.
 116      *
 117      * @return true if this object is enabled, false otherwise
 118      */
 119     public boolean isEnabled() {
 120         return enabled;
 121     }
 122 
 123     /**
 124      * When displaying the <code>JToolTip</code>, the
 125      * <code>ToolTipManager</code> chooses to use a lightweight
 126      * <code>JPanel</code> if it fits. This method allows you to
 127      * disable this feature. You have to do disable it if your
 128      * application mixes light weight and heavy weights components.
 129      *
 130      * @param aFlag true if a lightweight panel is desired, false otherwise
 131      *
 132      */
 133     public void setLightWeightPopupEnabled(boolean aFlag){
 134         lightWeightPopupEnabled = aFlag;
 135     }
 136 
 137     /**
 138      * Returns true if lightweight (all-Java) <code>Tooltips</code>
 139      * are in use, or false if heavyweight (native peer)
 140      * <code>Tooltips</code> are being used.
 141      *
 142      * @return true if lightweight <code>ToolTips</code> are in use
 143      */
 144     public boolean isLightWeightPopupEnabled() {
 145         return lightWeightPopupEnabled;
 146     }
 147 
 148 
 149     /**
 150      * Specifies the initial delay value.
 151      *
 152      * @param milliseconds  the number of milliseconds to delay
 153      *        (after the cursor has paused) before displaying the
 154      *        tooltip
 155      * @see #getInitialDelay
 156      */
 157     public void setInitialDelay(int milliseconds) {
 158         enterTimer.setInitialDelay(milliseconds);
 159     }
 160 
 161     /**
 162      * Returns the initial delay value.
 163      *
 164      * @return an integer representing the initial delay value,
 165      *          in milliseconds
 166      * @see #setInitialDelay
 167      */
 168     public int getInitialDelay() {
 169         return enterTimer.getInitialDelay();
 170     }
 171 
 172     /**
 173      * Specifies the dismissal delay value.
 174      *
 175      * @param milliseconds  the number of milliseconds to delay
 176      *        before taking away the tooltip
 177      * @see #getDismissDelay
 178      */
 179     public void setDismissDelay(int milliseconds) {
 180         insideTimer.setInitialDelay(milliseconds);
 181     }
 182 
 183     /**
 184      * Returns the dismissal delay value.
 185      *
 186      * @return an integer representing the dismissal delay value,
 187      *          in milliseconds
 188      * @see #setDismissDelay
 189      */
 190     public int getDismissDelay() {
 191         return insideTimer.getInitialDelay();
 192     }
 193 
 194     /**
 195      * Used to specify the amount of time before the user has to wait
 196      * <code>initialDelay</code> milliseconds before a tooltip will be
 197      * shown. That is, if the tooltip is hidden, and the user moves into
 198      * a region of the same Component that has a valid tooltip within
 199      * <code>milliseconds</code> milliseconds the tooltip will immediately
 200      * be shown. Otherwise, if the user moves into a region with a valid
 201      * tooltip after <code>milliseconds</code> milliseconds, the user
 202      * will have to wait an additional <code>initialDelay</code>
 203      * milliseconds before the tooltip is shown again.
 204      *
 205      * @param milliseconds time in milliseconds
 206      * @see #getReshowDelay
 207      */
 208     public void setReshowDelay(int milliseconds) {
 209         exitTimer.setInitialDelay(milliseconds);
 210     }
 211 
 212     /**
 213      * Returns the reshow delay property.
 214      *
 215      * @return reshown delay property
 216      * @see #setReshowDelay
 217      */
 218     public int getReshowDelay() {
 219         return exitTimer.getInitialDelay();
 220     }
 221 
 222     // Returns GraphicsConfiguration instance that toFind belongs to or null
 223     // if drawing point is set to a point beyond visible screen area (e.g.
 224     // Point(20000, 20000))
 225     private GraphicsConfiguration getDrawingGC(Point toFind) {
 226         GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
 227         GraphicsDevice devices[] = env.getScreenDevices();
 228         for (GraphicsDevice device : devices) {
 229             GraphicsConfiguration configs[] = device.getConfigurations();
 230             for (GraphicsConfiguration config : configs) {
 231                 Rectangle rect = config.getBounds();
 232                 if (rect.contains(toFind)) {
 233                     return config;
 234                 }
 235             }
 236         }
 237 
 238         return null;
 239     }
 240 
 241     void showTipWindow() {
 242         if(insideComponent == null || !insideComponent.isShowing())
 243             return;
 244         String mode = UIManager.getString("ToolTipManager.enableToolTipMode");
 245         if ("activeApplication".equals(mode)) {
 246             KeyboardFocusManager kfm =
 247                     KeyboardFocusManager.getCurrentKeyboardFocusManager();
 248             if (kfm.getFocusedWindow() == null) {
 249                 return;
 250             }
 251         }
 252         if (enabled) {
 253             Dimension size;
 254             Point screenLocation = insideComponent.getLocationOnScreen();
 255             Point location;
 256 
 257             Point toFind;
 258             if (preferredLocation != null) {
 259                 toFind = new Point(screenLocation.x + preferredLocation.x,
 260                         screenLocation.y + preferredLocation.y);
 261             } else {
 262                 toFind = mouseEvent.getLocationOnScreen();
 263             }
 264 
 265             GraphicsConfiguration gc = getDrawingGC(toFind);
 266             if (gc == null) {
 267                 toFind = mouseEvent.getLocationOnScreen();
 268                 gc = getDrawingGC(toFind);
 269                 if (gc == null) {
 270                     gc = insideComponent.getGraphicsConfiguration();
 271                 }
 272             }
 273 
 274             Rectangle sBounds = gc.getBounds();
 275             Insets screenInsets = Toolkit.getDefaultToolkit()
 276                                              .getScreenInsets(gc);
 277             // Take into account screen insets, decrease viewport
 278             sBounds.x += screenInsets.left;
 279             sBounds.y += screenInsets.top;
 280             sBounds.width -= (screenInsets.left + screenInsets.right);
 281             sBounds.height -= (screenInsets.top + screenInsets.bottom);
 282         boolean leftToRight
 283                 = SwingUtilities.isLeftToRight(insideComponent);
 284 
 285             // Just to be paranoid
 286             hideTipWindow();
 287 
 288             tip = insideComponent.createToolTip();
 289             tip.setTipText(toolTipText);
 290             size = tip.getPreferredSize();
 291 
 292             if(preferredLocation != null) {
 293                 location = toFind;
 294         if (!leftToRight) {
 295             location.x -= size.width;
 296         }
 297             } else {
 298                 location = new Point(screenLocation.x + mouseEvent.getX(),
 299                         screenLocation.y + mouseEvent.getY() + 20);
 300         if (!leftToRight) {
 301             if(location.x - size.width>=0) {
 302                 location.x -= size.width;
 303             }
 304         }
 305 
 306             }
 307 
 308         // we do not adjust x/y when using awt.Window tips
 309         if (popupRect == null){
 310         popupRect = new Rectangle();
 311         }
 312         popupRect.setBounds(location.x,location.y,
 313                 size.width,size.height);
 314 
 315         // Fit as much of the tooltip on screen as possible
 316             if (location.x < sBounds.x) {
 317                 location.x = sBounds.x;
 318             }
 319             else if (location.x - sBounds.x + size.width > sBounds.width) {
 320                 location.x = sBounds.x + Math.max(0, sBounds.width - size.width)
 321 ;
 322             }
 323             if (location.y < sBounds.y) {
 324                 location.y = sBounds.y;
 325             }
 326             else if (location.y - sBounds.y + size.height > sBounds.height) {
 327                 location.y = sBounds.y + Math.max(0, sBounds.height - size.height);
 328             }
 329 
 330             PopupFactory popupFactory = PopupFactory.getSharedInstance();
 331 
 332             if (lightWeightPopupEnabled) {
 333         int y = getPopupFitHeight(popupRect, insideComponent);
 334         int x = getPopupFitWidth(popupRect,insideComponent);
 335         if (x>0 || y>0) {
 336             popupFactory.setPopupType(PopupFactory.MEDIUM_WEIGHT_POPUP);
 337         } else {
 338             popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
 339         }
 340             }
 341             else {
 342                 popupFactory.setPopupType(PopupFactory.MEDIUM_WEIGHT_POPUP);
 343             }
 344         tipWindow = popupFactory.getPopup(insideComponent, tip,
 345                           location.x,
 346                           location.y);
 347             popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
 348 
 349         tipWindow.show();
 350 
 351             Window componentWindow = SwingUtilities.windowForComponent(
 352                                                     insideComponent);
 353 
 354             window = SwingUtilities.windowForComponent(tip);
 355             if (window != null && window != componentWindow) {
 356                 window.addMouseListener(this);
 357             }
 358             else {
 359                 window = null;
 360             }
 361 
 362             insideTimer.start();
 363         tipShowing = true;
 364         }
 365     }
 366 
 367     void hideTipWindow() {
 368         if (tipWindow != null) {
 369             if (window != null) {
 370                 window.removeMouseListener(this);
 371                 window = null;
 372             }
 373             tipWindow.hide();
 374             tipWindow = null;
 375             tipShowing = false;
 376             tip = null;
 377             insideTimer.stop();
 378         }
 379     }
 380 
 381     /**
 382      * Returns a shared <code>ToolTipManager</code> instance.
 383      *
 384      * @return a shared <code>ToolTipManager</code> object
 385      */
 386     public static ToolTipManager sharedInstance() {
 387         Object value = SwingUtilities.appContextGet(TOOL_TIP_MANAGER_KEY);
 388         if (value instanceof ToolTipManager) {
 389             return (ToolTipManager) value;
 390         }
 391         ToolTipManager manager = new ToolTipManager();
 392         SwingUtilities.appContextPut(TOOL_TIP_MANAGER_KEY, manager);
 393         return manager;
 394     }
 395 
 396     // add keylistener here to trigger tip for access
 397     /**
 398      * Registers a component for tooltip management.
 399      * <p>
 400      * This will register key bindings to show and hide the tooltip text
 401      * only if <code>component</code> has focus bindings. This is done
 402      * so that components that are not normally focus traversable, such
 403      * as <code>JLabel</code>, are not made focus traversable as a result
 404      * of invoking this method.
 405      *
 406      * @param component  a <code>JComponent</code> object to add
 407      * @see JComponent#isFocusTraversable
 408      */
 409     public void registerComponent(JComponent component) {
 410         component.removeMouseListener(this);
 411         component.addMouseListener(this);
 412         component.removeMouseMotionListener(moveBeforeEnterListener);
 413         component.addMouseMotionListener(moveBeforeEnterListener);
 414         component.removeKeyListener(accessibilityKeyListener);
 415         component.addKeyListener(accessibilityKeyListener);
 416     }
 417 
 418     /**
 419      * Removes a component from tooltip control.
 420      *
 421      * @param component  a <code>JComponent</code> object to remove
 422      */
 423     public void unregisterComponent(JComponent component) {
 424         component.removeMouseListener(this);
 425         component.removeMouseMotionListener(moveBeforeEnterListener);
 426         component.removeKeyListener(accessibilityKeyListener);
 427     }
 428 
 429     // implements java.awt.event.MouseListener
 430     /**
 431      *  Called when the mouse enters the region of a component.
 432      *  This determines whether the tool tip should be shown.
 433      *
 434      *  @param event  the event in question
 435      */
 436     public void mouseEntered(MouseEvent event) {
 437         initiateToolTip(event);
 438     }
 439 
 440     private void initiateToolTip(MouseEvent event) {
 441         if (event.getSource() == window) {
 442             return;
 443         }
 444         JComponent component = (JComponent)event.getSource();
 445         component.removeMouseMotionListener(moveBeforeEnterListener);
 446 
 447         exitTimer.stop();
 448 
 449         Point location = event.getPoint();
 450         // ensure tooltip shows only in proper place
 451         if (location.x < 0 ||
 452             location.x >=component.getWidth() ||
 453             location.y < 0 ||
 454             location.y >= component.getHeight()) {
 455             return;
 456         }
 457 
 458         if (insideComponent != null) {
 459             enterTimer.stop();
 460         }
 461         // A component in an unactive internal frame is sent two
 462         // mouseEntered events, make sure we don't end up adding
 463         // ourselves an extra time.
 464         component.removeMouseMotionListener(this);
 465         component.addMouseMotionListener(this);
 466 
 467         boolean sameComponent = (insideComponent == component);
 468 
 469         insideComponent = component;
 470     if (tipWindow != null){
 471             mouseEvent = event;
 472             if (showImmediately) {
 473                 String newToolTipText = component.getToolTipText(event);
 474                 Point newPreferredLocation = component.getToolTipLocation(
 475                                                          event);
 476                 boolean sameLoc = (preferredLocation != null) ?
 477                             preferredLocation.equals(newPreferredLocation) :
 478                             (newPreferredLocation == null);
 479 
 480                 if (!sameComponent || !Objects.equals(toolTipText, newToolTipText)
 481                         || !sameLoc) {
 482                     toolTipText = newToolTipText;
 483                     preferredLocation = newPreferredLocation;
 484                     showTipWindow();
 485                 }
 486             } else {
 487                 enterTimer.start();
 488             }
 489         }
 490     }
 491 
 492     // implements java.awt.event.MouseListener
 493     /**
 494      *  Called when the mouse exits the region of a component.
 495      *  Any tool tip showing should be hidden.
 496      *
 497      *  @param event  the event in question
 498      */
 499     public void mouseExited(MouseEvent event) {
 500         boolean shouldHide = true;
 501         if (insideComponent == null) {
 502             // Drag exit
 503         }
 504         if (window != null && event.getSource() == window && insideComponent != null) {
 505           // if we get an exit and have a heavy window
 506           // we need to check if it if overlapping the inside component
 507             Container insideComponentWindow = insideComponent.getTopLevelAncestor();
 508             // insideComponent may be removed after tooltip is made visible
 509             if (insideComponentWindow != null) {
 510                 Point location = event.getPoint();
 511                 SwingUtilities.convertPointToScreen(location, window);
 512 
 513                 location.x -= insideComponentWindow.getX();
 514                 location.y -= insideComponentWindow.getY();
 515 
 516                 location = SwingUtilities.convertPoint(null, location, insideComponent);
 517                 if (location.x >= 0 && location.x < insideComponent.getWidth() &&
 518                         location.y >= 0 && location.y < insideComponent.getHeight()) {
 519                     shouldHide = false;
 520                 } else {
 521                     shouldHide = true;
 522                 }
 523             }
 524         } else if(event.getSource() == insideComponent && tipWindow != null) {
 525             Window win = SwingUtilities.getWindowAncestor(insideComponent);
 526             if (win != null) {  // insideComponent may have been hidden (e.g. in a menu)
 527                 Point location = SwingUtilities.convertPoint(insideComponent,
 528                                                              event.getPoint(),
 529                                                              win);
 530                 Rectangle bounds = insideComponent.getTopLevelAncestor().getBounds();
 531                 location.x += bounds.x;
 532                 location.y += bounds.y;
 533 
 534                 Point loc = new Point(0, 0);
 535                 SwingUtilities.convertPointToScreen(loc, tip);
 536                 bounds.x = loc.x;
 537                 bounds.y = loc.y;
 538                 bounds.width = tip.getWidth();
 539                 bounds.height = tip.getHeight();
 540 
 541                 if (location.x >= bounds.x && location.x < (bounds.x + bounds.width) &&
 542                     location.y >= bounds.y && location.y < (bounds.y + bounds.height)) {
 543                     shouldHide = false;
 544                 } else {
 545                     shouldHide = true;
 546                 }
 547             }
 548         }
 549 
 550         if (shouldHide) {
 551             enterTimer.stop();
 552         if (insideComponent != null) {
 553                 insideComponent.removeMouseMotionListener(this);
 554             }
 555             insideComponent = null;
 556             toolTipText = null;
 557             mouseEvent = null;
 558             hideTipWindow();
 559             exitTimer.restart();
 560         }
 561     }
 562 
 563     // implements java.awt.event.MouseListener
 564     /**
 565      *  Called when the mouse is pressed.
 566      *  Any tool tip showing should be hidden.
 567      *
 568      *  @param event  the event in question
 569      */
 570     public void mousePressed(MouseEvent event) {
 571         hideTipWindow();
 572         enterTimer.stop();
 573         showImmediately = false;
 574         insideComponent = null;
 575         mouseEvent = null;
 576     }
 577 
 578     // implements java.awt.event.MouseMotionListener
 579     /**
 580      *  Called when the mouse is pressed and dragged.
 581      *  Does nothing.
 582      *
 583      *  @param event  the event in question
 584      */
 585     public void mouseDragged(MouseEvent event) {
 586     }
 587 
 588     // implements java.awt.event.MouseMotionListener
 589     /**
 590      *  Called when the mouse is moved.
 591      *  Determines whether the tool tip should be displayed.
 592      *
 593      *  @param event  the event in question
 594      */
 595     public void mouseMoved(MouseEvent event) {
 596         if (tipShowing) {
 597             checkForTipChange(event);
 598         }
 599         else if (showImmediately) {
 600             JComponent component = (JComponent)event.getSource();
 601             toolTipText = component.getToolTipText(event);
 602             if (toolTipText != null) {
 603                 preferredLocation = component.getToolTipLocation(event);
 604                 mouseEvent = event;
 605                 insideComponent = component;
 606                 exitTimer.stop();
 607                 showTipWindow();
 608             }
 609         }
 610         else {
 611             // Lazily lookup the values from within insideTimerAction
 612             insideComponent = (JComponent)event.getSource();
 613             mouseEvent = event;
 614             toolTipText = null;
 615             enterTimer.restart();
 616         }
 617     }
 618 
 619     /**
 620      * Checks to see if the tooltip needs to be changed in response to
 621      * the MouseMoved event <code>event</code>.
 622      */
 623     private void checkForTipChange(MouseEvent event) {
 624         JComponent component = (JComponent)event.getSource();
 625         String newText = component.getToolTipText(event);
 626         Point  newPreferredLocation = component.getToolTipLocation(event);
 627 
 628         if (newText != null || newPreferredLocation != null) {
 629             mouseEvent = event;
 630             if (((newText != null && newText.equals(toolTipText)) || newText == null) &&
 631                 ((newPreferredLocation != null && newPreferredLocation.equals(preferredLocation))
 632                  || newPreferredLocation == null)) {
 633                 if (tipWindow != null) {
 634                     insideTimer.restart();
 635                 } else {
 636                     enterTimer.restart();
 637                 }
 638             } else {
 639                 toolTipText = newText;
 640                 preferredLocation = newPreferredLocation;
 641                 if (showImmediately) {
 642                     hideTipWindow();
 643                     showTipWindow();
 644                     exitTimer.stop();
 645                 } else {
 646                     enterTimer.restart();
 647                 }
 648             }
 649         } else {
 650             toolTipText = null;
 651             preferredLocation = null;
 652             mouseEvent = null;
 653             insideComponent = null;
 654             hideTipWindow();
 655             enterTimer.stop();
 656             exitTimer.restart();
 657         }
 658     }
 659 
 660     protected class insideTimerAction implements ActionListener {
 661         public void actionPerformed(ActionEvent e) {
 662             if(insideComponent != null && insideComponent.isShowing()) {
 663                 // Lazy lookup
 664                 if (toolTipText == null && mouseEvent != null) {
 665                     toolTipText = insideComponent.getToolTipText(mouseEvent);
 666                     preferredLocation = insideComponent.getToolTipLocation(
 667                                               mouseEvent);
 668                 }
 669                 if(toolTipText != null) {
 670                     showImmediately = true;
 671                     showTipWindow();
 672                 }
 673                 else {
 674                     insideComponent = null;
 675                     toolTipText = null;
 676                     preferredLocation = null;
 677                     mouseEvent = null;
 678                     hideTipWindow();
 679                 }
 680             }
 681         }
 682     }
 683 
 684     protected class outsideTimerAction implements ActionListener {
 685         public void actionPerformed(ActionEvent e) {
 686             showImmediately = false;
 687         }
 688     }
 689 
 690     protected class stillInsideTimerAction implements ActionListener {
 691         public void actionPerformed(ActionEvent e) {
 692             hideTipWindow();
 693             enterTimer.stop();
 694             showImmediately = false;
 695             insideComponent = null;
 696             mouseEvent = null;
 697         }
 698     }
 699 
 700   /* This listener is registered when the tooltip is first registered
 701    * on a component in order to catch the situation where the tooltip
 702    * was turned on while the mouse was already within the bounds of
 703    * the component.  This way, the tooltip will be initiated on a
 704    * mouse-entered or mouse-moved, whichever occurs first.  Once the
 705    * tooltip has been initiated, we can remove this listener and rely
 706    * solely on mouse-entered to initiate the tooltip.
 707    */
 708     private class MoveBeforeEnterListener extends MouseMotionAdapter {
 709         public void mouseMoved(MouseEvent e) {
 710             initiateToolTip(e);
 711         }
 712     }
 713 
 714     static Frame frameForComponent(Component component) {
 715         while (!(component instanceof Frame)) {
 716             component = component.getParent();
 717         }
 718         return (Frame)component;
 719     }
 720 
 721   private FocusListener createFocusChangeListener(){
 722     return new FocusAdapter(){
 723       public void focusLost(FocusEvent evt){
 724         hideTipWindow();
 725         insideComponent = null;
 726         JComponent c = (JComponent)evt.getSource();
 727         c.removeFocusListener(focusChangeListener);
 728       }
 729     };
 730   }
 731 
 732   // Returns: 0 no adjust
 733   //         -1 can't fit
 734   //         >0 adjust value by amount returned
 735   private int getPopupFitWidth(Rectangle popupRectInScreen, Component invoker){
 736     if (invoker != null){
 737       Container parent;
 738       for (parent = invoker.getParent(); parent != null; parent = parent.getParent()){
 739         // fix internal frame size bug: 4139087 - 4159012
 740         if(parent instanceof JFrame || parent instanceof JDialog ||
 741            parent instanceof JWindow) { // no check for awt.Frame since we use Heavy tips
 742           return getWidthAdjust(parent.getBounds(),popupRectInScreen);
 743         } else if (parent instanceof JApplet || parent instanceof JInternalFrame) {
 744           if (popupFrameRect == null){
 745             popupFrameRect = new Rectangle();
 746           }
 747           Point p = parent.getLocationOnScreen();
 748           popupFrameRect.setBounds(p.x,p.y,
 749                                    parent.getBounds().width,
 750                                    parent.getBounds().height);
 751           return getWidthAdjust(popupFrameRect,popupRectInScreen);
 752         }
 753       }
 754     }
 755     return 0;
 756   }
 757 
 758   // Returns:  0 no adjust
 759   //          >0 adjust by value return
 760   private int getPopupFitHeight(Rectangle popupRectInScreen, Component invoker){
 761     if (invoker != null){
 762       Container parent;
 763       for (parent = invoker.getParent(); parent != null; parent = parent.getParent()){
 764         if(parent instanceof JFrame || parent instanceof JDialog ||
 765            parent instanceof JWindow) {
 766           return getHeightAdjust(parent.getBounds(),popupRectInScreen);
 767         } else if (parent instanceof JApplet || parent instanceof JInternalFrame) {
 768           if (popupFrameRect == null){
 769             popupFrameRect = new Rectangle();
 770           }
 771           Point p = parent.getLocationOnScreen();
 772           popupFrameRect.setBounds(p.x,p.y,
 773                                    parent.getBounds().width,
 774                                    parent.getBounds().height);
 775           return getHeightAdjust(popupFrameRect,popupRectInScreen);
 776         }
 777       }
 778     }
 779     return 0;
 780   }
 781 
 782   private int getHeightAdjust(Rectangle a, Rectangle b){
 783     if (b.y >= a.y && (b.y + b.height) <= (a.y + a.height))
 784       return 0;
 785     else
 786       return (((b.y + b.height) - (a.y + a.height)) + 5);
 787   }
 788 
 789   // Return the number of pixels over the edge we are extending.
 790   // If we are over the edge the ToolTipManager can adjust.
 791   // REMIND: what if the Tooltip is just too big to fit at all - we currently will just clip
 792   private int getWidthAdjust(Rectangle a, Rectangle b){
 793     //    System.out.println("width b.x/b.width: " + b.x + "/" + b.width +
 794     //                 "a.x/a.width: " + a.x + "/" + a.width);
 795     if (b.x >= a.x && (b.x + b.width) <= (a.x + a.width)){
 796       return 0;
 797     }
 798     else {
 799       return (((b.x + b.width) - (a.x +a.width)) + 5);
 800     }
 801   }
 802 
 803 
 804     //
 805     // Actions
 806     //
 807     private void show(JComponent source) {
 808         if (tipWindow != null) { // showing we unshow
 809             hideTipWindow();
 810             insideComponent = null;
 811         }
 812         else {
 813             hideTipWindow(); // be safe
 814             enterTimer.stop();
 815             exitTimer.stop();
 816             insideTimer.stop();
 817             insideComponent = source;
 818             if (insideComponent != null){
 819                 toolTipText = insideComponent.getToolTipText();
 820                 preferredLocation = new Point(10,insideComponent.getHeight()+
 821                                               10);  // manual set
 822                 showTipWindow();
 823                 // put a focuschange listener on to bring the tip down
 824                 if (focusChangeListener == null){
 825                     focusChangeListener = createFocusChangeListener();
 826                 }
 827                 insideComponent.addFocusListener(focusChangeListener);
 828             }
 829         }
 830     }
 831 
 832     private void hide(JComponent source) {
 833         hideTipWindow();
 834         source.removeFocusListener(focusChangeListener);
 835         preferredLocation = null;
 836         insideComponent = null;
 837     }
 838 
 839     /* This listener is registered when the tooltip is first registered
 840      * on a component in order to process accessibility keybindings.
 841      * This will apply globally across L&F
 842      *
 843      * Post Tip: Ctrl+F1
 844      * Unpost Tip: Esc and Ctrl+F1
 845      */
 846     private class AccessibilityKeyListener extends KeyAdapter {
 847         public void keyPressed(KeyEvent e) {
 848             if (!e.isConsumed()) {
 849                 JComponent source = (JComponent) e.getComponent();
 850                 KeyStroke keyStrokeForEvent = KeyStroke.getKeyStrokeForEvent(e);
 851                 if (hideTip.equals(keyStrokeForEvent)) {
 852                     if (tipWindow != null) {
 853                         hide(source);
 854                         e.consume();
 855                     }
 856                 } else if (postTip.equals(keyStrokeForEvent)) {
 857                     // Shown tooltip will be hidden
 858                     ToolTipManager.this.show(source);
 859                     e.consume();
 860                 }
 861             }
 862         }
 863     }
 864 }