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 private static Map<Window, List<HeavyWeightPopup>> getHeavyWeightPopupCache() { 405 synchronized (HeavyWeightPopup.class) { 406 Map<Window, List<HeavyWeightPopup>> cache = (Map<Window, List<HeavyWeightPopup>>)SwingUtilities.appContextGet( 407 heavyWeightPopupCacheKey); 408 409 if (cache == null) { 410 cache = new HashMap<Window, List<HeavyWeightPopup>>(2); 411 SwingUtilities.appContextPut(heavyWeightPopupCacheKey, 412 cache); 413 } 414 return cache; 415 } 416 } 417 418 /** 419 * Recycles the passed in <code>HeavyWeightPopup</code>. 420 */ 421 private static void recycleHeavyWeightPopup(HeavyWeightPopup popup) { 422 synchronized (HeavyWeightPopup.class) { 423 List<HeavyWeightPopup> cache; 424 Window window = SwingUtilities.getWindowAncestor( 425 popup.getComponent()); 426 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache(); 427 428 if (window instanceof Popup.DefaultFrame || 429 !window.isVisible()) { 430 // If the Window isn't visible, we don't cache it as we 431 // likely won't ever get a windowClosed event to clean up. 432 // We also don't cache DefaultFrames as this indicates 433 // there wasn't a valid Window parent, and thus we don't 434 // know when to clean up. 435 popup._dispose(); 436 return; 437 } else if (heavyPopupCache.containsKey(window)) { 438 cache = heavyPopupCache.get(window); 439 } else { 440 cache = new ArrayList<HeavyWeightPopup>(); 441 heavyPopupCache.put(window, cache); 442 // Clean up if the Window is closed 443 final Window w = window; 444 445 w.addWindowListener(new WindowAdapter() { 446 public void windowClosed(WindowEvent e) { 447 List<HeavyWeightPopup> popups; 448 449 synchronized(HeavyWeightPopup.class) { 450 Map<Window, List<HeavyWeightPopup>> heavyPopupCache2 = 451 getHeavyWeightPopupCache(); 452 453 popups = heavyPopupCache2.remove(w); 454 } 455 if (popups != null) { 456 for (int counter = popups.size() - 1; 457 counter >= 0; counter--) { 458 popups.get(counter)._dispose(); 459 } 460 } 461 } 462 }); 463 } 464 465 if(cache.size() < MAX_CACHE_SIZE) { 466 cache.add(popup); 467 } else { 468 popup._dispose(); 469 } 470 } 471 } 472 473 /** 474 * Enables or disables cache for current object. 475 */ 476 void setCacheEnabled(boolean enable) { 477 isCacheEnabled = enable; 478 } 479 480 // 481 // Popup methods 482 // 483 public void hide() { 484 super.hide(); 485 if (isCacheEnabled) { 486 recycleHeavyWeightPopup(this); 487 } else { 488 this._dispose(); 489 } 490 } 491 492 /** 493 * As we recycle the <code>Window</code>, we don't want to dispose it, 494 * thus this method does nothing, instead use <code>_dipose</code> 495 * which will handle the disposing. 496 */ 497 void dispose() { 498 } 499 500 void _dispose() { 501 super.dispose(); 502 } 503 } 504 505 506 507 /** 508 * ContainerPopup consolidates the common code used in the light/medium 509 * weight implementations of <code>Popup</code>. 510 */ 511 private static class ContainerPopup extends Popup { 512 /** Component we are to be added to. */ 513 Component owner; 514 /** Desired x location. */ 515 int x; 516 /** Desired y location. */ 517 int y; 518 519 public void hide() { 520 Component component = getComponent(); 521 522 if (component != null) { 523 Container parent = component.getParent(); 524 525 if (parent != null) { 526 Rectangle bounds = component.getBounds(); 527 528 parent.remove(component); 529 parent.repaint(bounds.x, bounds.y, bounds.width, 530 bounds.height); 531 } 532 } 533 owner = null; 534 } 535 public void pack() { 536 Component component = getComponent(); 537 538 if (component != null) { 539 component.setSize(component.getPreferredSize()); 540 } 541 } 542 543 void reset(Component owner, Component contents, int ownerX, 544 int ownerY) { 545 if ((owner instanceof JFrame) || (owner instanceof JDialog) || 546 (owner instanceof JWindow)) { 547 // Force the content to be added to the layered pane, otherwise 548 // we'll get an exception when adding to the RootPaneContainer. 549 owner = ((RootPaneContainer)owner).getLayeredPane(); 550 } 551 super.reset(owner, contents, ownerX, ownerY); 552 553 x = ownerX; 554 y = ownerY; 555 this.owner = owner; 556 } 557 558 boolean overlappedByOwnedWindow() { 559 Component component = getComponent(); 560 if(owner != null && component != null) { 561 Window w = SwingUtilities.getWindowAncestor(owner); 562 if (w == null) { 563 return false; 564 } 565 Window[] ownedWindows = w.getOwnedWindows(); 566 if(ownedWindows != null) { 567 Rectangle bnd = component.getBounds(); 568 for (Window window : ownedWindows) { 569 if (window.isVisible() && 570 bnd.intersects(window.getBounds())) { 571 572 return true; 573 } 574 } 575 } 576 } 577 return false; 578 } 579 580 /** 581 * Returns true if popup can fit the screen and the owner's top parent. 582 * It determines can popup be lightweight or mediumweight. 583 */ 584 boolean fitsOnScreen() { 585 boolean result = false; 586 Component component = getComponent(); 587 if (owner != null && component != null) { 588 int popupWidth = component.getWidth(); 589 int popupHeight = component.getHeight(); 590 591 Container parent = (Container) SwingUtilities.getRoot(owner); 592 if (parent instanceof JFrame || 593 parent instanceof JDialog || 594 parent instanceof JWindow) { 595 596 Rectangle parentBounds = parent.getBounds(); 597 Insets i = parent.getInsets(); 598 parentBounds.x += i.left; 599 parentBounds.y += i.top; 600 parentBounds.width -= i.left + i.right; 601 parentBounds.height -= i.top + i.bottom; 602 603 if (JPopupMenu.canPopupOverlapTaskBar()) { 604 GraphicsConfiguration gc = 605 parent.getGraphicsConfiguration(); 606 Rectangle popupArea = getContainerPopupArea(gc); 607 result = parentBounds.intersection(popupArea) 608 .contains(x, y, popupWidth, popupHeight); 609 } else { 610 result = parentBounds 611 .contains(x, y, popupWidth, popupHeight); 612 } 613 } else if (parent instanceof JApplet) { 614 Rectangle parentBounds = parent.getBounds(); 615 Point p = parent.getLocationOnScreen(); 616 parentBounds.x = p.x; 617 parentBounds.y = p.y; 618 result = parentBounds.contains(x, y, popupWidth, popupHeight); 619 } 620 } 621 return result; 622 } 623 624 Rectangle getContainerPopupArea(GraphicsConfiguration gc) { 625 Rectangle screenBounds; 626 Toolkit toolkit = Toolkit.getDefaultToolkit(); 627 Insets insets; 628 if(gc != null) { 629 // If we have GraphicsConfiguration use it 630 // to get screen bounds 631 screenBounds = gc.getBounds(); 632 insets = toolkit.getScreenInsets(gc); 633 } else { 634 // If we don't have GraphicsConfiguration use primary screen 635 screenBounds = new Rectangle(toolkit.getScreenSize()); 636 insets = new Insets(0, 0, 0, 0); 637 } 638 // Take insets into account 639 screenBounds.x += insets.left; 640 screenBounds.y += insets.top; 641 screenBounds.width -= (insets.left + insets.right); 642 screenBounds.height -= (insets.top + insets.bottom); 643 return screenBounds; 644 } 645 } 646 647 648 /** 649 * Popup implementation that is used in headless environment. 650 */ 651 private static class HeadlessPopup extends ContainerPopup { 652 static Popup getHeadlessPopup(Component owner, Component contents, 653 int ownerX, int ownerY) { 654 HeadlessPopup popup = new HeadlessPopup(); 655 popup.reset(owner, contents, ownerX, ownerY); 656 return popup; 657 } 658 659 Component createComponent(Component owner) { 660 return new Panel(new BorderLayout()); 661 } 662 663 public void show() { 664 } 665 public void hide() { 666 } 667 } 668 669 670 /** 671 * Popup implementation that uses a JPanel as the popup. 672 */ 673 private static class LightWeightPopup extends ContainerPopup { 674 private static final Object lightWeightPopupCacheKey = 675 new StringBuffer("PopupFactory.lightPopupCache"); 676 677 /** 678 * Returns a light weight <code>Popup</code> implementation. If 679 * the <code>Popup</code> needs more space that in available in 680 * <code>owner</code>, this will return null. 681 */ 682 static Popup getLightWeightPopup(Component owner, Component contents, 683 int ownerX, int ownerY) { 684 LightWeightPopup popup = getRecycledLightWeightPopup(); 685 686 if (popup == null) { 687 popup = new LightWeightPopup(); 688 } 689 popup.reset(owner, contents, ownerX, ownerY); 690 if (!popup.fitsOnScreen() || 691 popup.overlappedByOwnedWindow()) { 692 popup.hide(); 693 return null; 694 } 695 return popup; 696 } 697 698 /** 699 * Returns the cache to use for heavy weight popups. 700 */ 701 private static List<LightWeightPopup> getLightWeightPopupCache() { 702 List<LightWeightPopup> cache = (List<LightWeightPopup>)SwingUtilities.appContextGet( 703 lightWeightPopupCacheKey); 704 if (cache == null) { 705 cache = new ArrayList<LightWeightPopup>(); 706 SwingUtilities.appContextPut(lightWeightPopupCacheKey, cache); 707 } 708 return cache; 709 } 710 711 /** 712 * Recycles the LightWeightPopup <code>popup</code>. 713 */ 714 private static void recycleLightWeightPopup(LightWeightPopup popup) { 715 synchronized (LightWeightPopup.class) { 716 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache(); 717 if (lightPopupCache.size() < MAX_CACHE_SIZE) { 718 lightPopupCache.add(popup); 719 } 720 } 721 } 722 723 /** 724 * Returns a previously used <code>LightWeightPopup</code>, or null 725 * if none of the popups have been recycled. 726 */ 727 private static LightWeightPopup getRecycledLightWeightPopup() { 728 synchronized (LightWeightPopup.class) { 729 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache(); 730 if (lightPopupCache.size() > 0) { 731 LightWeightPopup r = lightPopupCache.get(0); 732 lightPopupCache.remove(0); 733 return r; 734 } 735 return null; 736 } 737 } 738 739 740 741 // 742 // Popup methods 743 // 744 public void hide() { 745 super.hide(); 746 747 Container component = (Container)getComponent(); 748 749 component.removeAll(); 750 recycleLightWeightPopup(this); 751 } 752 public void show() { 753 Container parent = null; 754 755 if (owner != null) { 756 parent = (owner instanceof Container? (Container)owner : owner.getParent()); 757 } 758 759 // Try to find a JLayeredPane and Window to add 760 for (Container p = parent; p != null; p = p.getParent()) { 761 if (p instanceof JRootPane) { 762 if (p.getParent() instanceof JInternalFrame) { 763 continue; 764 } 765 parent = ((JRootPane)p).getLayeredPane(); 766 // Continue, so that if there is a higher JRootPane, we'll 767 // pick it up. 768 } else if(p instanceof Window) { 769 if (parent == null) { 770 parent = p; 771 } 772 break; 773 } else if (p instanceof JApplet) { 774 // Painting code stops at Applets, we don't want 775 // to add to a Component above an Applet otherwise 776 // you'll never see it painted. 777 break; 778 } 779 } 780 781 Point p = SwingUtilities.convertScreenLocationToParent(parent, x, 782 y); 783 Component component = getComponent(); 784 785 component.setLocation(p.x, p.y); 786 if (parent instanceof JLayeredPane) { 787 parent.add(component, JLayeredPane.POPUP_LAYER, 0); 788 } else { 789 parent.add(component); 790 } 791 } 792 793 Component createComponent(Component owner) { 794 JComponent component = new JPanel(new BorderLayout(), true); 795 796 component.setOpaque(true); 797 return component; 798 } 799 800 // 801 // Local methods 802 // 803 804 /** 805 * Resets the <code>Popup</code> to an initial state. 806 */ 807 void reset(Component owner, Component contents, int ownerX, 808 int ownerY) { 809 super.reset(owner, contents, ownerX, ownerY); 810 811 JComponent component = (JComponent)getComponent(); 812 813 component.setOpaque(contents.isOpaque()); 814 component.setLocation(ownerX, ownerY); 815 component.add(contents, BorderLayout.CENTER); 816 contents.invalidate(); 817 pack(); 818 } 819 } 820 821 822 /** 823 * Popup implementation that uses a Panel as the popup. 824 */ 825 private static class MediumWeightPopup extends ContainerPopup { 826 private static final Object mediumWeightPopupCacheKey = 827 new StringBuffer("PopupFactory.mediumPopupCache"); 828 829 /** Child of the panel. The contents are added to this. */ 830 private JRootPane rootPane; 831 832 833 /** 834 * Returns a medium weight <code>Popup</code> implementation. If 835 * the <code>Popup</code> needs more space that in available in 836 * <code>owner</code>, this will return null. 837 */ 838 static Popup getMediumWeightPopup(Component owner, Component contents, 839 int ownerX, int ownerY) { 840 MediumWeightPopup popup = getRecycledMediumWeightPopup(); 841 842 if (popup == null) { 843 popup = new MediumWeightPopup(); 844 } 845 popup.reset(owner, contents, ownerX, ownerY); 846 if (!popup.fitsOnScreen() || 847 popup.overlappedByOwnedWindow()) { 848 popup.hide(); 849 return null; 850 } 851 return popup; 852 } 853 854 /** 855 * Returns the cache to use for medium weight popups. 856 */ 857 private static List<MediumWeightPopup> getMediumWeightPopupCache() { 858 List<MediumWeightPopup> cache = (List<MediumWeightPopup>)SwingUtilities.appContextGet( 859 mediumWeightPopupCacheKey); 860 861 if (cache == null) { 862 cache = new ArrayList<MediumWeightPopup>(); 863 SwingUtilities.appContextPut(mediumWeightPopupCacheKey, cache); 864 } 865 return cache; 866 } 867 868 /** 869 * Recycles the MediumWeightPopup <code>popup</code>. 870 */ 871 private static void recycleMediumWeightPopup(MediumWeightPopup popup) { 872 synchronized (MediumWeightPopup.class) { 873 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache(); 874 if (mediumPopupCache.size() < MAX_CACHE_SIZE) { 875 mediumPopupCache.add(popup); 876 } 877 } 878 } 879 880 /** 881 * Returns a previously used <code>MediumWeightPopup</code>, or null 882 * if none of the popups have been recycled. 883 */ 884 private static MediumWeightPopup getRecycledMediumWeightPopup() { 885 synchronized (MediumWeightPopup.class) { 886 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache(); 887 if (mediumPopupCache.size() > 0) { 888 MediumWeightPopup r = mediumPopupCache.get(0); 889 mediumPopupCache.remove(0); 890 return r; 891 } 892 return null; 893 } 894 } 895 896 897 // 898 // Popup 899 // 900 901 public void hide() { 902 super.hide(); 903 rootPane.getContentPane().removeAll(); 904 recycleMediumWeightPopup(this); 905 } 906 public void show() { 907 Component component = getComponent(); 908 Container parent = null; 909 910 if (owner != null) { 911 parent = owner.getParent(); 912 } 913 /* 914 Find the top level window, 915 if it has a layered pane, 916 add to that, otherwise 917 add to the window. */ 918 while (!(parent instanceof Window || parent instanceof Applet) && 919 (parent!=null)) { 920 parent = parent.getParent(); 921 } 922 // Set the visibility to false before adding to workaround a 923 // bug in Solaris in which the Popup gets added at the wrong 924 // location, which will result in a mouseExit, which will then 925 // result in the ToolTip being removed. 926 if (parent instanceof RootPaneContainer) { 927 parent = ((RootPaneContainer)parent).getLayeredPane(); 928 Point p = SwingUtilities.convertScreenLocationToParent(parent, 929 x, y); 930 component.setVisible(false); 931 component.setLocation(p.x, p.y); 932 parent.add(component, JLayeredPane.POPUP_LAYER, 933 0); 934 } else { 935 Point p = SwingUtilities.convertScreenLocationToParent(parent, 936 x, y); 937 938 component.setLocation(p.x, p.y); 939 component.setVisible(false); 940 parent.add(component); 941 } 942 component.setVisible(true); 943 } 944 945 Component createComponent(Component owner) { 946 Panel component = new MediumWeightComponent(); 947 948 rootPane = new JRootPane(); 949 // NOTE: this uses setOpaque vs LookAndFeel.installProperty as 950 // there is NO reason for the RootPane not to be opaque. For 951 // painting to work the contentPane must be opaque, therefor the 952 // RootPane can also be opaque. 953 rootPane.setOpaque(true); 954 component.add(rootPane, BorderLayout.CENTER); 955 return component; 956 } 957 958 /** 959 * Resets the <code>Popup</code> to an initial state. 960 */ 961 void reset(Component owner, Component contents, int ownerX, 962 int ownerY) { 963 super.reset(owner, contents, ownerX, ownerY); 964 965 Component component = getComponent(); 966 967 component.setLocation(ownerX, ownerY); 968 rootPane.getContentPane().add(contents, BorderLayout.CENTER); 969 contents.invalidate(); 970 component.validate(); 971 pack(); 972 } 973 974 975 // This implements SwingHeavyWeight so that repaints on it 976 // are processed by the RepaintManager and SwingPaintEventDispatcher. 977 @SuppressWarnings("serial") // JDK-implementation class 978 private static class MediumWeightComponent extends Panel implements 979 SwingHeavyWeight { 980 MediumWeightComponent() { 981 super(new BorderLayout()); 982 } 983 } 984 } 985 }