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