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