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