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