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