1/*
2 * Copyright (c) 1997, 2016, 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
27package javax.swing;
28
29import java.awt.event.*;
30import java.awt.*;
31import 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 */
55public 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}
889