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