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