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