1 /* 2 * Copyright (c) 1997, 2018, 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 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 */ 55 public 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 }