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