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