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