1 /* 2 * Copyright (c) 1999, 2014, 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 package javax.swing; 27 28 import sun.awt.EmbeddedFrame; 29 import sun.awt.OSInfo; 30 import sun.swing.SwingAccessor; 31 32 import java.applet.Applet; 33 import java.awt.*; 34 import java.awt.event.WindowAdapter; 35 import java.awt.event.WindowEvent; 36 import java.security.AccessController; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import static javax.swing.ClientPropertyKey.PopupFactory_FORCE_HEAVYWEIGHT_POPUP; 42 43 /** 44 * {@code PopupFactory}, as the name implies, is used to obtain 45 * instances of {@code Popup}s. {@code Popup}s are used to 46 * display a {@code Component} above all other {@code Component}s 47 * in a particular containment hierarchy. The general contract is that 48 * once you have obtained a {@code Popup} from a 49 * {@code PopupFactory}, you must invoke {@code hide} on the 50 * {@code Popup}. The typical usage is: 51 * <pre> 52 * PopupFactory factory = PopupFactory.getSharedInstance(); 53 * Popup popup = factory.getPopup(owner, contents, x, y); 54 * popup.show(); 55 * ... 56 * popup.hide(); 57 * </pre> 58 * 59 * @see Popup 60 * 61 * @since 1.4 62 */ 63 public class PopupFactory { 64 65 static { 66 SwingAccessor.setPopupFactoryAccessor(new SwingAccessor.PopupFactoryAccessor() { 67 @Override 68 public Popup getHeavyWeightPopup(PopupFactory factory, Component owner, 69 Component contents, int ownerX, int ownerY) { 70 return factory.getPopup(owner, contents, ownerX, ownerY, HEAVY_WEIGHT_POPUP); 71 } 72 }); 73 } 74 /** 75 * The shared instanceof {@code PopupFactory} is per 76 * {@code AppContext}. This is the key used in the 77 * {@code AppContext} to locate the {@code PopupFactory}. 78 */ 79 private static final Object SharedInstanceKey = 80 new StringBuffer("PopupFactory.SharedInstanceKey"); 81 82 /** 83 * Max number of items to store in any one particular cache. 84 */ 85 private static final int MAX_CACHE_SIZE = 5; 86 87 /** 88 * Key used to indicate a light weight popup should be used. 89 */ 90 static final int LIGHT_WEIGHT_POPUP = 0; 91 92 /** 93 * Key used to indicate a medium weight Popup should be used. 94 */ 95 static final int MEDIUM_WEIGHT_POPUP = 1; 96 97 /* 98 * Key used to indicate a heavy weight Popup should be used. 99 */ 100 static final int HEAVY_WEIGHT_POPUP = 2; 101 102 /** 103 * Default type of Popup to create. 104 */ 105 private int popupType = LIGHT_WEIGHT_POPUP; 106 107 108 /** 109 * Sets the {@code PopupFactory} that will be used to obtain 110 * {@code Popup}s. 111 * This will throw an {@code IllegalArgumentException} if 112 * {@code factory} is null. 113 * 114 * @param factory Shared PopupFactory 115 * @exception IllegalArgumentException if {@code factory} is null 116 * @see #getPopup 117 */ 118 public static void setSharedInstance(PopupFactory factory) { 119 if (factory == null) { 120 throw new IllegalArgumentException("PopupFactory can not be null"); 121 } 122 SwingUtilities.appContextPut(SharedInstanceKey, factory); 123 } 124 125 /** 126 * Returns the shared {@code PopupFactory} which can be used 127 * to obtain {@code Popup}s. 128 * 129 * @return Shared PopupFactory 130 */ 131 public static PopupFactory getSharedInstance() { 132 PopupFactory factory = (PopupFactory)SwingUtilities.appContextGet( 133 SharedInstanceKey); 134 135 if (factory == null) { 136 factory = new PopupFactory(); 137 setSharedInstance(factory); 138 } 139 return factory; 140 } 141 142 143 /** 144 * Provides a hint as to the type of {@code Popup} that should 145 * be created. 146 */ 147 void setPopupType(int type) { 148 popupType = type; 149 } 150 151 /** 152 * Returns the preferred type of Popup to create. 153 */ 154 int getPopupType() { 155 return popupType; 156 } 157 158 /** 159 * Creates a {@code Popup} for the Component {@code owner} 160 * containing the Component {@code contents}. {@code owner} 161 * is used to determine which {@code Window} the new 162 * {@code Popup} will parent the {@code Component} the 163 * {@code Popup} creates to. A null {@code owner} implies there 164 * is no valid parent. {@code x} and 165 * {@code y} specify the preferred initial location to place 166 * the {@code Popup} at. Based on screen size, or other paramaters, 167 * the {@code Popup} may not display at {@code x} and 168 * {@code y}. 169 * 170 * @param owner Component mouse coordinates are relative to, may be null 171 * @param contents Contents of the Popup 172 * @param x Initial x screen coordinate 173 * @param y Initial y screen coordinate 174 * @exception IllegalArgumentException if contents is null 175 * @return Popup containing Contents 176 */ 177 public Popup getPopup(Component owner, Component contents, 178 int x, int y) throws IllegalArgumentException { 179 if (contents == null) { 180 throw new IllegalArgumentException( 181 "Popup.getPopup must be passed non-null contents"); 182 } 183 184 int popupType = getPopupType(owner, contents, x, y); 185 Popup popup = getPopup(owner, contents, x, y, popupType); 186 187 if (popup == null) { 188 // Didn't fit, force to heavy. 189 popup = getPopup(owner, contents, x, y, HEAVY_WEIGHT_POPUP); 190 } 191 return popup; 192 } 193 194 /** 195 * Returns the popup type to use for the specified parameters. 196 */ 197 private int getPopupType(Component owner, Component contents, 198 int ownerX, int ownerY) { 199 int popupType = getPopupType(); 200 201 if (owner == null || invokerInHeavyWeightPopup(owner)) { 202 popupType = HEAVY_WEIGHT_POPUP; 203 } 204 else if (popupType == LIGHT_WEIGHT_POPUP && 205 !(contents instanceof JToolTip) && 206 !(contents instanceof JPopupMenu)) { 207 popupType = MEDIUM_WEIGHT_POPUP; 208 } 209 210 // Check if the parent component is an option pane. If so we need to 211 // force a heavy weight popup in order to have event dispatching work 212 // correctly. 213 Component c = owner; 214 while (c != null) { 215 if (c instanceof JComponent) { 216 if (((JComponent)c).getClientProperty( 217 PopupFactory_FORCE_HEAVYWEIGHT_POPUP) == Boolean.TRUE) { 218 popupType = HEAVY_WEIGHT_POPUP; 219 break; 220 } 221 } 222 c = c.getParent(); 223 } 224 225 return popupType; 226 } 227 228 /** 229 * Obtains the appropriate {@code Popup} based on 230 * {@code popupType}. 231 */ 232 private Popup getPopup(Component owner, Component contents, 233 int ownerX, int ownerY, int popupType) { 234 if (GraphicsEnvironment.isHeadless()) { 235 return getHeadlessPopup(owner, contents, ownerX, ownerY); 236 } 237 238 switch(popupType) { 239 case LIGHT_WEIGHT_POPUP: 240 return getLightWeightPopup(owner, contents, ownerX, ownerY); 241 case MEDIUM_WEIGHT_POPUP: 242 return getMediumWeightPopup(owner, contents, ownerX, ownerY); 243 case HEAVY_WEIGHT_POPUP: 244 Popup popup = getHeavyWeightPopup(owner, contents, ownerX, ownerY); 245 if ((AccessController.doPrivileged(OSInfo.getOSTypeAction()) == 246 OSInfo.OSType.MACOSX) && (owner != null) && 247 (EmbeddedFrame.getAppletIfAncestorOf(owner) != null)) { 248 ((HeavyWeightPopup)popup).setCacheEnabled(false); 249 } 250 return popup; 251 } 252 return null; 253 } 254 255 /** 256 * Creates a headless popup 257 */ 258 private Popup getHeadlessPopup(Component owner, Component contents, 259 int ownerX, int ownerY) { 260 return HeadlessPopup.getHeadlessPopup(owner, contents, ownerX, ownerY); 261 } 262 263 /** 264 * Creates a light weight popup. 265 */ 266 private Popup getLightWeightPopup(Component owner, Component contents, 267 int ownerX, int ownerY) { 268 return LightWeightPopup.getLightWeightPopup(owner, contents, ownerX, 269 ownerY); 270 } 271 272 /** 273 * Creates a medium weight popup. 274 */ 275 private Popup getMediumWeightPopup(Component owner, Component contents, 276 int ownerX, int ownerY) { 277 return MediumWeightPopup.getMediumWeightPopup(owner, contents, 278 ownerX, ownerY); 279 } 280 281 /** 282 * Creates a heavy weight popup. 283 */ 284 private Popup getHeavyWeightPopup(Component owner, Component contents, 285 int ownerX, int ownerY) { 286 if (GraphicsEnvironment.isHeadless()) { 287 return getMediumWeightPopup(owner, contents, ownerX, ownerY); 288 } 289 return HeavyWeightPopup.getHeavyWeightPopup(owner, contents, ownerX, 290 ownerY); 291 } 292 293 /** 294 * Returns true if the Component {@code i} inside a heavy weight 295 * {@code Popup}. 296 */ 297 private boolean invokerInHeavyWeightPopup(Component i) { 298 if (i != null) { 299 Container parent; 300 for(parent = i.getParent() ; parent != null ; parent = 301 parent.getParent()) { 302 if (parent instanceof Popup.HeavyWeightWindow) { 303 return true; 304 } 305 } 306 } 307 return false; 308 } 309 310 311 /** 312 * Popup implementation that uses a Window as the popup. 313 */ 314 private static class HeavyWeightPopup extends Popup { 315 private static final Object heavyWeightPopupCacheKey = 316 new StringBuffer("PopupFactory.heavyWeightPopupCache"); 317 318 private volatile boolean isCacheEnabled = true; 319 320 /** 321 * Returns either a new or recycled {@code Popup} containing 322 * the specified children. 323 */ 324 static Popup getHeavyWeightPopup(Component owner, Component contents, 325 int ownerX, int ownerY) { 326 Window window = (owner != null) ? SwingUtilities. 327 getWindowAncestor(owner) : null; 328 HeavyWeightPopup popup = null; 329 330 if (window != null) { 331 popup = getRecycledHeavyWeightPopup(window); 332 } 333 334 boolean focusPopup = false; 335 if(contents != null && contents.isFocusable()) { 336 if(contents instanceof JPopupMenu) { 337 JPopupMenu jpm = (JPopupMenu) contents; 338 Component popComps[] = jpm.getComponents(); 339 for (Component popComp : popComps) { 340 if (!(popComp instanceof MenuElement) && 341 !(popComp instanceof JSeparator)) { 342 focusPopup = true; 343 break; 344 } 345 } 346 } 347 } 348 349 if (popup == null || 350 ((JWindow) popup.getComponent()) 351 .getFocusableWindowState() != focusPopup) { 352 353 if(popup != null) { 354 // The recycled popup can't serve us well 355 // dispose it and create new one 356 popup._dispose(); 357 } 358 359 popup = new HeavyWeightPopup(); 360 } 361 362 popup.reset(owner, contents, ownerX, ownerY); 363 364 if(focusPopup) { 365 JWindow wnd = (JWindow) popup.getComponent(); 366 wnd.setFocusableWindowState(true); 367 // Set window name. We need this in BasicPopupMenuUI 368 // to identify focusable popup window. 369 wnd.setName("###focusableSwingPopup###"); 370 } 371 372 return popup; 373 } 374 375 /** 376 * Returns a previously disposed heavy weight {@code Popup} 377 * associated with {@code window}. This will return null if 378 * there is no {@code HeavyWeightPopup} associated with 379 * {@code window}. 380 */ 381 private static HeavyWeightPopup getRecycledHeavyWeightPopup(Window w) { 382 synchronized (HeavyWeightPopup.class) { 383 List<HeavyWeightPopup> cache; 384 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache(); 385 386 if (heavyPopupCache.containsKey(w)) { 387 cache = heavyPopupCache.get(w); 388 } else { 389 return null; 390 } 391 if (cache.size() > 0) { 392 HeavyWeightPopup r = cache.get(0); 393 cache.remove(0); 394 return r; 395 } 396 return null; 397 } 398 } 399 400 /** 401 * Returns the cache to use for heavy weight popups. Maps from 402 * {@code Window} to a {@code List} of 403 * {@code HeavyWeightPopup}s. 404 */ 405 @SuppressWarnings("unchecked") 406 private static Map<Window, List<HeavyWeightPopup>> getHeavyWeightPopupCache() { 407 synchronized (HeavyWeightPopup.class) { 408 Map<Window, List<HeavyWeightPopup>> cache = (Map<Window, List<HeavyWeightPopup>>)SwingUtilities.appContextGet( 409 heavyWeightPopupCacheKey); 410 411 if (cache == null) { 412 cache = new HashMap<>(2); 413 SwingUtilities.appContextPut(heavyWeightPopupCacheKey, 414 cache); 415 } 416 return cache; 417 } 418 } 419 420 /** 421 * Recycles the passed in {@code HeavyWeightPopup}. 422 */ 423 private static void recycleHeavyWeightPopup(HeavyWeightPopup popup) { 424 synchronized (HeavyWeightPopup.class) { 425 List<HeavyWeightPopup> cache; 426 Window window = SwingUtilities.getWindowAncestor( 427 popup.getComponent()); 428 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache(); 429 430 if (window instanceof Popup.DefaultFrame || 431 !window.isVisible()) { 432 // If the Window isn't visible, we don't cache it as we 433 // likely won't ever get a windowClosed event to clean up. 434 // We also don't cache DefaultFrames as this indicates 435 // there wasn't a valid Window parent, and thus we don't 436 // know when to clean up. 437 popup._dispose(); 438 return; 439 } else if (heavyPopupCache.containsKey(window)) { 440 cache = heavyPopupCache.get(window); 441 } else { 442 cache = new ArrayList<HeavyWeightPopup>(); 443 heavyPopupCache.put(window, cache); 444 // Clean up if the Window is closed 445 final Window w = window; 446 447 w.addWindowListener(new WindowAdapter() { 448 public void windowClosed(WindowEvent e) { 449 List<HeavyWeightPopup> popups; 450 451 synchronized(HeavyWeightPopup.class) { 452 Map<Window, List<HeavyWeightPopup>> heavyPopupCache2 = 453 getHeavyWeightPopupCache(); 454 455 popups = heavyPopupCache2.remove(w); 456 } 457 if (popups != null) { 458 for (int counter = popups.size() - 1; 459 counter >= 0; counter--) { 460 popups.get(counter)._dispose(); 461 } 462 } 463 } 464 }); 465 } 466 467 if(cache.size() < MAX_CACHE_SIZE) { 468 cache.add(popup); 469 } else { 470 popup._dispose(); 471 } 472 } 473 } 474 475 /** 476 * Enables or disables cache for current object. 477 */ 478 void setCacheEnabled(boolean enable) { 479 isCacheEnabled = enable; 480 } 481 482 // 483 // Popup methods 484 // 485 public void hide() { 486 super.hide(); 487 if (isCacheEnabled) { 488 recycleHeavyWeightPopup(this); 489 } else { 490 this._dispose(); 491 } 492 } 493 494 /** 495 * As we recycle the {@code Window}, we don't want to dispose it, 496 * thus this method does nothing, instead use {@code _dipose} 497 * which will handle the disposing. 498 */ 499 void dispose() { 500 } 501 502 void _dispose() { 503 super.dispose(); 504 } 505 } 506 507 508 509 /** 510 * ContainerPopup consolidates the common code used in the light/medium 511 * weight implementations of {@code Popup}. 512 */ 513 private static class ContainerPopup extends Popup { 514 /** Component we are to be added to. */ 515 Component owner; 516 /** Desired x location. */ 517 int x; 518 /** Desired y location. */ 519 int y; 520 521 public void hide() { 522 Component component = getComponent(); 523 524 if (component != null) { 525 Container parent = component.getParent(); 526 527 if (parent != null) { 528 Rectangle bounds = component.getBounds(); 529 530 parent.remove(component); 531 parent.repaint(bounds.x, bounds.y, bounds.width, 532 bounds.height); 533 } 534 } 535 owner = null; 536 } 537 public void pack() { 538 Component component = getComponent(); 539 540 if (component != null) { 541 component.setSize(component.getPreferredSize()); 542 } 543 } 544 545 void reset(Component owner, Component contents, int ownerX, 546 int ownerY) { 547 if ((owner instanceof JFrame) || (owner instanceof JDialog) || 548 (owner instanceof JWindow)) { 549 // Force the content to be added to the layered pane, otherwise 550 // we'll get an exception when adding to the RootPaneContainer. 551 owner = ((RootPaneContainer)owner).getLayeredPane(); 552 } 553 super.reset(owner, contents, ownerX, ownerY); 554 555 x = ownerX; 556 y = ownerY; 557 this.owner = owner; 558 } 559 560 boolean overlappedByOwnedWindow() { 561 Component component = getComponent(); 562 if(owner != null && component != null) { 563 Window w = SwingUtilities.getWindowAncestor(owner); 564 if (w == null) { 565 return false; 566 } 567 Window[] ownedWindows = w.getOwnedWindows(); 568 if(ownedWindows != null) { 569 Rectangle bnd = component.getBounds(); 570 for (Window window : ownedWindows) { 571 if (window.isVisible() && 572 bnd.intersects(window.getBounds())) { 573 574 return true; 575 } 576 } 577 } 578 } 579 return false; 580 } 581 582 /** 583 * Returns true if popup can fit the screen and the owner's top parent. 584 * It determines can popup be lightweight or mediumweight. 585 */ 586 boolean fitsOnScreen() { 587 boolean result = false; 588 Component component = getComponent(); 589 if (owner != null && component != null) { 590 int popupWidth = component.getWidth(); 591 int popupHeight = component.getHeight(); 592 593 Container parent = (Container) SwingUtilities.getRoot(owner); 594 if (parent instanceof JFrame || 595 parent instanceof JDialog || 596 parent instanceof JWindow) { 597 598 Rectangle parentBounds = parent.getBounds(); 599 Insets i = parent.getInsets(); 600 parentBounds.x += i.left; 601 parentBounds.y += i.top; 602 parentBounds.width -= i.left + i.right; 603 parentBounds.height -= i.top + i.bottom; 604 605 if (JPopupMenu.canPopupOverlapTaskBar()) { 606 GraphicsConfiguration gc = 607 parent.getGraphicsConfiguration(); 608 Rectangle popupArea = getContainerPopupArea(gc); 609 result = parentBounds.intersection(popupArea) 610 .contains(x, y, popupWidth, popupHeight); 611 } else { 612 result = parentBounds 613 .contains(x, y, popupWidth, popupHeight); 614 } 615 } else if (parent instanceof JApplet) { 616 Rectangle parentBounds = parent.getBounds(); 617 Point p = parent.getLocationOnScreen(); 618 parentBounds.x = p.x; 619 parentBounds.y = p.y; 620 result = parentBounds.contains(x, y, popupWidth, popupHeight); 621 } 622 } 623 return result; 624 } 625 626 Rectangle getContainerPopupArea(GraphicsConfiguration gc) { 627 Rectangle screenBounds; 628 Toolkit toolkit = Toolkit.getDefaultToolkit(); 629 Insets insets; 630 if(gc != null) { 631 // If we have GraphicsConfiguration use it 632 // to get screen bounds 633 screenBounds = gc.getBounds(); 634 insets = toolkit.getScreenInsets(gc); 635 } else { 636 // If we don't have GraphicsConfiguration use primary screen 637 screenBounds = new Rectangle(toolkit.getScreenSize()); 638 insets = new Insets(0, 0, 0, 0); 639 } 640 // Take insets into account 641 screenBounds.x += insets.left; 642 screenBounds.y += insets.top; 643 screenBounds.width -= (insets.left + insets.right); 644 screenBounds.height -= (insets.top + insets.bottom); 645 return screenBounds; 646 } 647 } 648 649 650 /** 651 * Popup implementation that is used in headless environment. 652 */ 653 private static class HeadlessPopup extends ContainerPopup { 654 static Popup getHeadlessPopup(Component owner, Component contents, 655 int ownerX, int ownerY) { 656 HeadlessPopup popup = new HeadlessPopup(); 657 popup.reset(owner, contents, ownerX, ownerY); 658 return popup; 659 } 660 661 Component createComponent(Component owner) { 662 return new Panel(new BorderLayout()); 663 } 664 665 public void show() { 666 } 667 public void hide() { 668 } 669 } 670 671 672 /** 673 * Popup implementation that uses a JPanel as the popup. 674 */ 675 private static class LightWeightPopup extends ContainerPopup { 676 private static final Object lightWeightPopupCacheKey = 677 new StringBuffer("PopupFactory.lightPopupCache"); 678 679 /** 680 * Returns a light weight {@code Popup} implementation. If 681 * the {@code Popup} needs more space that in available in 682 * {@code owner}, this will return null. 683 */ 684 static Popup getLightWeightPopup(Component owner, Component contents, 685 int ownerX, int ownerY) { 686 LightWeightPopup popup = getRecycledLightWeightPopup(); 687 688 if (popup == null) { 689 popup = new LightWeightPopup(); 690 } 691 popup.reset(owner, contents, ownerX, ownerY); 692 if (!popup.fitsOnScreen() || 693 popup.overlappedByOwnedWindow()) { 694 popup.hide(); 695 return null; 696 } 697 return popup; 698 } 699 700 /** 701 * Returns the cache to use for heavy weight popups. 702 */ 703 @SuppressWarnings("unchecked") 704 private static List<LightWeightPopup> getLightWeightPopupCache() { 705 List<LightWeightPopup> cache = (List<LightWeightPopup>)SwingUtilities.appContextGet( 706 lightWeightPopupCacheKey); 707 if (cache == null) { 708 cache = new ArrayList<>(); 709 SwingUtilities.appContextPut(lightWeightPopupCacheKey, cache); 710 } 711 return cache; 712 } 713 714 /** 715 * Recycles the LightWeightPopup {@code popup}. 716 */ 717 private static void recycleLightWeightPopup(LightWeightPopup popup) { 718 synchronized (LightWeightPopup.class) { 719 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache(); 720 if (lightPopupCache.size() < MAX_CACHE_SIZE) { 721 lightPopupCache.add(popup); 722 } 723 } 724 } 725 726 /** 727 * Returns a previously used {@code LightWeightPopup}, or null 728 * if none of the popups have been recycled. 729 */ 730 private static LightWeightPopup getRecycledLightWeightPopup() { 731 synchronized (LightWeightPopup.class) { 732 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache(); 733 if (lightPopupCache.size() > 0) { 734 LightWeightPopup r = lightPopupCache.get(0); 735 lightPopupCache.remove(0); 736 return r; 737 } 738 return null; 739 } 740 } 741 742 743 744 // 745 // Popup methods 746 // 747 public void hide() { 748 super.hide(); 749 750 Container component = (Container)getComponent(); 751 752 component.removeAll(); 753 recycleLightWeightPopup(this); 754 } 755 public void show() { 756 Container parent = null; 757 758 if (owner != null) { 759 parent = (owner instanceof Container? (Container)owner : owner.getParent()); 760 } 761 762 // Try to find a JLayeredPane and Window to add 763 for (Container p = parent; p != null; p = p.getParent()) { 764 if (p instanceof JRootPane) { 765 if (p.getParent() instanceof JInternalFrame) { 766 continue; 767 } 768 parent = ((JRootPane)p).getLayeredPane(); 769 // Continue, so that if there is a higher JRootPane, we'll 770 // pick it up. 771 } else if(p instanceof Window) { 772 if (parent == null) { 773 parent = p; 774 } 775 break; 776 } else if (p instanceof JApplet) { 777 // Painting code stops at Applets, we don't want 778 // to add to a Component above an Applet otherwise 779 // you'll never see it painted. 780 break; 781 } 782 } 783 784 Point p = SwingUtilities.convertScreenLocationToParent(parent, x, 785 y); 786 Component component = getComponent(); 787 788 component.setLocation(p.x, p.y); 789 if (parent instanceof JLayeredPane) { 790 parent.add(component, JLayeredPane.POPUP_LAYER, 0); 791 } else { 792 parent.add(component); 793 } 794 } 795 796 Component createComponent(Component owner) { 797 JComponent component = new JPanel(new BorderLayout(), true); 798 799 component.setOpaque(true); 800 return component; 801 } 802 803 // 804 // Local methods 805 // 806 807 /** 808 * Resets the {@code Popup} to an initial state. 809 */ 810 void reset(Component owner, Component contents, int ownerX, 811 int ownerY) { 812 super.reset(owner, contents, ownerX, ownerY); 813 814 JComponent component = (JComponent)getComponent(); 815 816 component.setOpaque(contents.isOpaque()); 817 component.setLocation(ownerX, ownerY); 818 component.add(contents, BorderLayout.CENTER); 819 contents.invalidate(); 820 pack(); 821 } 822 } 823 824 825 /** 826 * Popup implementation that uses a Panel as the popup. 827 */ 828 private static class MediumWeightPopup extends ContainerPopup { 829 private static final Object mediumWeightPopupCacheKey = 830 new StringBuffer("PopupFactory.mediumPopupCache"); 831 832 /** Child of the panel. The contents are added to this. */ 833 private JRootPane rootPane; 834 835 836 /** 837 * Returns a medium weight {@code Popup} implementation. If 838 * the {@code Popup} needs more space that in available in 839 * {@code owner}, this will return null. 840 */ 841 static Popup getMediumWeightPopup(Component owner, Component contents, 842 int ownerX, int ownerY) { 843 MediumWeightPopup popup = getRecycledMediumWeightPopup(); 844 845 if (popup == null) { 846 popup = new MediumWeightPopup(); 847 } 848 popup.reset(owner, contents, ownerX, ownerY); 849 if (!popup.fitsOnScreen() || 850 popup.overlappedByOwnedWindow()) { 851 popup.hide(); 852 return null; 853 } 854 return popup; 855 } 856 857 /** 858 * Returns the cache to use for medium weight popups. 859 */ 860 @SuppressWarnings("unchecked") 861 private static List<MediumWeightPopup> getMediumWeightPopupCache() { 862 List<MediumWeightPopup> cache = (List<MediumWeightPopup>)SwingUtilities.appContextGet( 863 mediumWeightPopupCacheKey); 864 865 if (cache == null) { 866 cache = new ArrayList<>(); 867 SwingUtilities.appContextPut(mediumWeightPopupCacheKey, cache); 868 } 869 return cache; 870 } 871 872 /** 873 * Recycles the MediumWeightPopup {@code popup}. 874 */ 875 private static void recycleMediumWeightPopup(MediumWeightPopup popup) { 876 synchronized (MediumWeightPopup.class) { 877 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache(); 878 if (mediumPopupCache.size() < MAX_CACHE_SIZE) { 879 mediumPopupCache.add(popup); 880 } 881 } 882 } 883 884 /** 885 * Returns a previously used {@code MediumWeightPopup}, or null 886 * if none of the popups have been recycled. 887 */ 888 private static MediumWeightPopup getRecycledMediumWeightPopup() { 889 synchronized (MediumWeightPopup.class) { 890 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache(); 891 if (mediumPopupCache.size() > 0) { 892 MediumWeightPopup r = mediumPopupCache.get(0); 893 mediumPopupCache.remove(0); 894 return r; 895 } 896 return null; 897 } 898 } 899 900 901 // 902 // Popup 903 // 904 905 public void hide() { 906 super.hide(); 907 rootPane.getContentPane().removeAll(); 908 recycleMediumWeightPopup(this); 909 } 910 public void show() { 911 Component component = getComponent(); 912 Container parent = null; 913 914 if (owner != null) { 915 parent = owner.getParent(); 916 } 917 /* 918 Find the top level window, 919 if it has a layered pane, 920 add to that, otherwise 921 add to the window. */ 922 while (!(parent instanceof Window || parent instanceof Applet) && 923 (parent!=null)) { 924 parent = parent.getParent(); 925 } 926 // Set the visibility to false before adding to workaround a 927 // bug in Solaris in which the Popup gets added at the wrong 928 // location, which will result in a mouseExit, which will then 929 // result in the ToolTip being removed. 930 if (parent instanceof RootPaneContainer) { 931 parent = ((RootPaneContainer)parent).getLayeredPane(); 932 Point p = SwingUtilities.convertScreenLocationToParent(parent, 933 x, y); 934 component.setVisible(false); 935 component.setLocation(p.x, p.y); 936 parent.add(component, JLayeredPane.POPUP_LAYER, 937 0); 938 } else { 939 Point p = SwingUtilities.convertScreenLocationToParent(parent, 940 x, y); 941 942 component.setLocation(p.x, p.y); 943 component.setVisible(false); 944 parent.add(component); 945 } 946 component.setVisible(true); 947 } 948 949 Component createComponent(Component owner) { 950 Panel component = new MediumWeightComponent(); 951 952 rootPane = new JRootPane(); 953 // NOTE: this uses setOpaque vs LookAndFeel.installProperty as 954 // there is NO reason for the RootPane not to be opaque. For 955 // painting to work the contentPane must be opaque, therefor the 956 // RootPane can also be opaque. 957 rootPane.setOpaque(true); 958 component.add(rootPane, BorderLayout.CENTER); 959 return component; 960 } 961 962 /** 963 * Resets the {@code Popup} to an initial state. 964 */ 965 void reset(Component owner, Component contents, int ownerX, 966 int ownerY) { 967 super.reset(owner, contents, ownerX, ownerY); 968 969 Component component = getComponent(); 970 971 component.setLocation(ownerX, ownerY); 972 rootPane.getContentPane().add(contents, BorderLayout.CENTER); 973 contents.invalidate(); 974 component.validate(); 975 pack(); 976 } 977 978 979 // This implements SwingHeavyWeight so that repaints on it 980 // are processed by the RepaintManager and SwingPaintEventDispatcher. 981 @SuppressWarnings("serial") // JDK-implementation class 982 private static class MediumWeightComponent extends Panel implements 983 SwingHeavyWeight { 984 MediumWeightComponent() { 985 super(new BorderLayout()); 986 } 987 } 988 } 989 }