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