Popup & PopupFactory

The bugtraq report that corresponds to this change is: 4303635.

Certain Components, such as JPopupMenu and JToolTip, present themselves above all other Components in a particular containment hierarchy. Rather than each of these Components containing the same code to obtain the same behavior, they delegate to a Popup which can be obtained from a PopupFactory. A Popup is able to display a Component at a particular location on the screen. Based on the size and location of the requested Component, PopupFactory will return an appropriate Popup.

Currently Popup and PopupFactory are package private. Apple has requested that for JDK 1.4 we expose these classes to enable custom look and feel implementations the ability to create their own Popup. This will allow Apple to position menus in an appropriate manner for their look and feel. In reviewing the current API we decided to make a handful of changes, resulting in:

PopupFactory will be defined by:

/**
 * PopupFactory, as the name implies, is used to obtain
 * instances of Popups. Popups are used to
 * display a Component above all other Components
 * in a particular containment hierarchy. The general contract is that
 * once you have obtained a Popup from a
 * PopupFactory, you must invoke hide on the
 * Popup. The typical usage is:
 * 
 *   PopupFactory factory = PopupFactory.getSharedInstance();
 *   Popup popup = factory.getPopup(owner, contents, x, y);
 *   popup.show();
 *   ...
 *   popup.hide();
 * 
 *
 * @see Popup
 *
 * @version %I% %G%
 * @since 1.4
 */
public class PopupFactory {
    /**
     * Sets the AppContext specific PopupFactory.
     * This will throw an IllegalArgumentException if
     * factory is null.
     *
     * @param factory Shared PopupFactory
     * @exception IllegalArgumentException if
     *      factory is null
     */
    public static void setSharedInstance(PopupFactory factory);

    /**
     * Returns the shared PopupFactory which can be used
     * to obtain Popups.
     *
     * @return shared PopupFactory
     */
    public static PopupFactory getSharedInstance();

    /**
     * Creates a Popup for the Component
     * owner containing the Component contents.
     * owner is used to determine which Window
     * the new Popup will parent the Component the
     * Popup creates to. A null owner
     * implies there is no valid parent. x and
     * y specify the preferred initial location to place
     * the Popup at. Based on screen size, or other paramaters,
     * the Popup may not display at x and
     * y.
     *
     * @param owner    component mouse coordinates are relative to,
     *                 may be null
     * @param contents contents of the Popup
     * @param x        initial x screen coordinate
     * @param y        initial y screen coordinate
     * @exception IllegalArgumentException if contents is null
     * @return Popup containing contents
     */
    public Popup getPopup(Component owner, Component contents,
                          int x, int y) throws IllegalArgumentException;
    

And Popup will be defined by:

/**
 * Popups are used to display a Component to the user, typically
 * on top of all the other Components in a particular containment
 * hierarchy. Popups have a very small life cycle. Once you
 * have obtained a Popup, and hidden it (invoked the
 * hide method), you should no longer
 * invoke any methods on it. This allows the PopupFactory to cache
 * Popups for later use.
 * 

* The general contract is that if you need to change the size of the * Component, or location of the Popup, you should * obtain a new Popup. *

* Popup does not descend from Component, rather * implementations of Popup are responsible for creating * and maintaining their own Components to render the * requested Component to the user. *

* You typically do not explicitly create an instance of Popup, * instead obtain one from a PopupFactory. * * @see PopupFactory * * @version %I% %G% * @since 1.4 */ public class Popup { /** * Creates a Popup for the Component owner * containing the Component contents. owner * is used to determine which Window the new * Popup will parent the Component the * Popup creates to. * A null owner implies there is no * valid parent. x and * y specify the preferred initial location to place * the Popup at. Based on screen size, or other paramaters, * the Popup may not display at x and * y. * * @param owner component mouse coordinates are relative to, may be null * @param contents contents of the Popup * @param x initial x screen coordinate * @param y initial y screen coordinate * @exception IllegalArgumentException if contents is * null * @return Popup containing contents */ protected Popup(Component owner, Component contents, int x, int y); /** * Creates a Popup. This is provided for subclasses. */ protected Popup(); /** * Makes the Popup visible. If the Popup is * currently visible, this has no effect. */ public void show(); /** * Hides and disposes of the Popup. Once a Popup * has been disposed you should no longer invoke methods on it. A * disposed Popup may be reclaimed and later used * based on the PopupFactory. As such, if you invoke methods * on a disposed Popup, indeterminate * behavior will result. */ public void hide();

To enable the Popup used by JPopupMenu to be replaced by the UI, we will add the following to PopupMenuUI:

    /**
     * Returns the Popup that will be responsible for
     * displaying the JPopupMenu.
     *
     * @param popup JPopupMenu requesting Popup
     * @param x     screen x location where Popup is to be shown
     * @param y     screen y location where Popup is to be shown
     * @return Popup that will show the JPopupMenu
     * @since 1.4
     */
    public Popup getPopup(JPopupMenu popup, int x, int y);
    

PopupMenuUI.getPopups implementation will obtain the Popup from the shared PopupFactory, but custom look and feel implementations can override this and return whatever Popup they desire.

Semantic Changes

JPopupMenu currently defines the method setLocation. In pre-merlin this method would invoke setLocation on the Popup, but as we are removing this method from Popup, JPopupMenu will now recreate the Popup. We don't believe this is a problem, as generally developers don't change the location of visible popups. And the only time this would really cause a problem is if the developer was some how relying on the JPopupMenus parent only changing when the Popopup was hidden, again, quite unlikely. A quick grep through the source code reveals that internally we only call this from show, which typically isn't invoked when the Popup is visible.

JPopupMenu also defines the method setPopupSize, which would invoke setSize on the Popup if it was visible. Internally we will change this method to set the preferred size of the JPopupMenu, which will give the same results. If the JPopupMenu is visible when this is invoked, the Popup will be recreated. Again, we don't feel this is much of an issue as typically the size doesn't change once the popup is visible, and it would really only effect developers if they were listening for containment changing and doing something rather dubious. The javadoc will change to reflect the new behavior:

    /**
     * Sets the size of the Popup window using a Dimension object.
     * This is equivalent to setPreferredSize(d).
     *
     * @param d   the Dimension specifying the new size 
     * of this component.
     * @beaninfo
     * description: The size of the popup menu
     */
    public void setPopupSize(Dimension d);

    /**
     * Sets the size of the Popup window to the specified width and
     * height. This is equivalent to
     *  setPreferredSize(new Dimension(width, height)).
     *
     * @param width the new width of the Popup in pixels
     * @param height the new height of the Popup in pixels
     * @beaninfo
     * description: The size of the popup menu
     */
    public void setPopupSize(int width, int height);

Last modified: Wed Dec 13 14:15:23 PST 2000