cc.alcina.framework.gwt.client.widget.dialog.RelativePopupPanel.java Source code

Java tutorial

Introduction

Here is the source code for cc.alcina.framework.gwt.client.widget.dialog.RelativePopupPanel.java

Source

/*
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cc.alcina.framework.gwt.client.widget.dialog;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.HasAnimation;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.PopupImpl;

import cc.alcina.framework.common.client.collections.CollectionFilter;

/**
 * A panel that can "pop up" over other widgets. It overlays the browser's
 * client area (and any previously-created popups).
 * 
 * <p>
 * A RelativePopupPanel should not generally be added to other panels; rather,
 * it should be shown and hidden using the {@link #show()} and {@link #hide()}
 * methods.
 * </p>
 * <p>
 * The width and height of the RelativePopupPanel cannot be explicitly set; they
 * are determined by the RelativePopupPanel's widget. Calls to
 * {@link #setWidth(String)} and {@link #setHeight(String)} will call these
 * methods on the RelativePopupPanel's widget.
 * </p>
 * <p>
 * <img class='gallery' src='doc-files/RelativePopupPanel.png'/>
 * </p>
 * 
 * <p>
 * The RelativePopupPanel can be optionally displayed with a "glass" element
 * behind it, which is commonly used to gray out the widgets behind it. It can
 * be enabled using {@link #setGlassEnabled(boolean)}. It has a default style
 * name of "gwt-PopupPanelGlass", which can be changed using
 * {@link #setGlassStyleName(String)}.
 * </p>
 * 
 * <p>
 * <h3>Example</h3> {@example com.google.gwt.examples.PopupPanelExample}
 * </p>
 * <h3>CSS Style Rules</h3>
 * <dl>
 * <dt>.gwt-RelativePopupPanel</dt>
 * <dd>the outside of the popup</dd>
 * <dt>.gwt-RelativePopupPanel .popupContent</dt>
 * <dd>the wrapper around the content</dd>
 * <dt>.gwt-PopupPanelGlass</dt>
 * <dd>the glass background behind the popup</dd>
 * </dl>
 */
@SuppressWarnings("deprecation")
public class RelativePopupPanel extends SimplePanel implements HasAnimation, HasCloseHandlers<RelativePopupPanel> {
    /**
     * The duration of the animation.
     */
    private static final int ANIMATION_DURATION = 200;

    /**
     * The default style name.
     */
    private static final String DEFAULT_STYLENAME = "gwt-PopupPanel";

    private static final PopupImpl impl = GWT.create(PopupImpl.class);

    /**
     * Window resize handler used to keep the glass the proper size.
     */
    private ResizeHandler glassResizer = new ResizeHandler() {
        public void onResize(ResizeEvent event) {
            Style style = glass.getStyle();
            int winWidth = Window.getClientWidth();
            int winHeight = Window.getClientHeight();
            // Hide the glass while checking the document size. Otherwise it
            // would
            // interfere with the measurement.
            style.setDisplay(Display.NONE);
            style.setWidth(0, Unit.PX);
            style.setHeight(0, Unit.PX);
            int width = Document.get().getScrollWidth();
            int height = Document.get().getScrollHeight();
            // Set the glass size to the larger of the window's client size or
            // the
            // document's scroll size.
            style.setWidth(Math.max(width, winWidth), Unit.PX);
            style.setHeight(Math.max(height, winHeight), Unit.PX);
            // The size is set. Show the glass again.
            style.setDisplay(Display.BLOCK);
        }
    };

    /**
     * If true, animate the opening of this popup from the center. If false,
     * animate it open from top to bottom, and do not animate closing. Use false
     * to animate menus.
     */
    private AnimationType animType = AnimationType.CENTER;

    private boolean autoHide, previewAllNativeEvents, modal, showing;

    private boolean autoHideOnHistoryEvents;

    private boolean hideOnEscape;

    private List<Element> autoHidePartners;

    // Used to track requested size across changing child widgets
    private String desiredHeight;

    private String desiredWidth;

    /**
     * The glass element.
     */
    private Element glass;

    private String glassStyleName = "gwt-PopupPanelGlass";

    /**
     * A boolean indicating that a glass element should be used.
     */
    private boolean isGlassEnabled;

    private boolean isAnimationEnabled = false;

    // the left style attribute in pixels
    private int leftPosition = -1;

    private HandlerRegistration nativePreviewHandlerRegistration;

    private HandlerRegistration historyHandlerRegistration;

    /**
     * The {@link ResizeAnimation} used to open and close the
     * {@link RelativePopupPanel}s.
     */
    private ResizeAnimation resizeAnimation = new ResizeAnimation(this);

    // The top style attribute in pixels
    private int topPosition = -1;

    private ComplexPanel positioningContainer;

    /**
     * Creates an empty popup panel. A child widget must be added to it before
     * it is shown.
     */
    public RelativePopupPanel() {
        super();
        super.getContainerElement().appendChild(impl.createElement());
        // Default position of popup should be in the upper-left corner of the
        // window. By setting a default position, the popup will not appear in
        // an undefined location if it is shown before its position is set.
        setPopupPosition(0, 0);
        setStyleName(DEFAULT_STYLENAME);
        setStyleName(getContainerElement(), "popupContent");
    }

    /**
     * Creates an empty popup panel, specifying its "auto-hide" property.
     * 
     * @param autoHide
     *            <code>true</code> if the popup should be automatically hidden
     *            when the user clicks outside of it or the history token
     *            changes.
     */
    public RelativePopupPanel(boolean autoHide) {
        this();
        this.autoHide = autoHide;
        this.autoHideOnHistoryEvents = autoHide;
    }

    /**
     * Creates an empty popup panel, specifying its "auto-hide" and "modal"
     * properties.
     * 
     * @param autoHide
     *            <code>true</code> if the popup should be automatically hidden
     *            when the user clicks outside of it or the history token
     *            changes.
     * @param modal
     *            <code>true</code> if keyboard or mouse events that do not
     *            target the RelativePopupPanel or its children should be
     *            ignored
     */
    public RelativePopupPanel(boolean autoHide, boolean modal) {
        this(autoHide);
        this.modal = modal;
    }

    /**
     * Mouse events that occur within an autoHide partner will not hide a panel
     * set to autoHide.
     * 
     * @param partner
     *            the auto hide partner to add
     */
    public void addAutoHidePartner(Element partner) {
        assert partner != null : "partner cannot be null";
        if (autoHidePartners == null) {
            autoHidePartners = new ArrayList<Element>();
        }
        autoHidePartners.add(partner);
    }

    public HandlerRegistration addCloseHandler(CloseHandler<RelativePopupPanel> handler) {
        return addHandler(handler, CloseEvent.getType());
    }

    /**
     * Centers the popup in the browser window and shows it. If the popup was
     * already showing, then the popup is centered.
     */
    public void center() {
        boolean initiallyShowing = showing;
        boolean initiallyAnimated = isAnimationEnabled;
        if (!initiallyShowing) {
            setVisible(false);
            setAnimationEnabled(false);
            show();
        }
        int left = (Window.getClientWidth() - getOffsetWidth()) >> 1;
        int top = (Window.getClientHeight() - getOffsetHeight()) >> 1;
        setPopupPosition(Math.max(Window.getScrollLeft() + left, 0), Math.max(Window.getScrollTop() + top, 0));
        if (!initiallyShowing) {
            setAnimationEnabled(initiallyAnimated);
            // Run the animation. The popup is already visible, so we can skip
            // the
            // call to setState.
            if (initiallyAnimated) {
                impl.setClip(getElement(), "rect(0px, 0px, 0px, 0px)");
                setVisible(true);
                resizeAnimation.run(ANIMATION_DURATION);
            } else {
                setVisible(true);
            }
        }
    }

    /**
     * Gets the style name to be used on the glass element. By default, this is
     * "gwt-PopupPanelGlass".
     * 
     * @return the glass element's style name
     */
    public String getGlassStyleName() {
        return glassStyleName;
    }

    /**
     * Gets the panel's offset height in pixels. Calls to
     * {@link #setHeight(String)} before the panel's child widget is set will
     * not influence the offset height.
     * 
     * @return the object's offset height
     */
    @Override
    public int getOffsetHeight() {
        return super.getOffsetHeight();
    }

    /**
     * Gets the panel's offset width in pixels. Calls to
     * {@link #setWidth(String)} before the panel's child widget is set will not
     * influence the offset width.
     * 
     * @return the object's offset width
     */
    @Override
    public int getOffsetWidth() {
        return super.getOffsetWidth();
    }

    /**
     * Gets the popup's left position relative to the browser's client area.
     * 
     * @return the popup's left position
     */
    public int getPopupLeft() {
        return DOM.getAbsoluteLeft(getElement());
    }

    /**
     * Gets the popup's top position relative to the browser's client area.
     * 
     * @return the popup's top position
     */
    public int getPopupTop() {
        return DOM.getAbsoluteTop(getElement());
    }

    public ComplexPanel getPositioningContainer() {
        return positioningContainer;
    }

    @Override
    public String getTitle() {
        return DOM.getElementProperty(getContainerElement(), "title");
    }

    /**
     * Hides the popup and detaches it from the page. This has no effect if it
     * is not currently showing.
     */
    public void hide() {
        hide(false);
    }

    /**
     * Hides the popup and detaches it from the page. This has no effect if it
     * is not currently showing.
     * 
     * @param autoClosved
     *            the value that will be passed to
     *            {@link CloseHandler#onClose(CloseEvent)} when the popup is
     *            closed
     */
    public void hide(boolean autoClosed) {
        if (!isShowing()) {
            return;
        }
        setState(false, true);
        CloseEvent.fire(this, this, autoClosed);
    }

    public boolean isAnimationEnabled() {
        return isAnimationEnabled;
    }

    /**
     * Returns <code>true</code> if the popup should be automatically hidden
     * when the user clicks outside of it.
     * 
     * @return true if autoHide is enabled, false if disabled
     */
    public boolean isAutoHideEnabled() {
        return autoHide;
    }

    /**
     * Returns <code>true</code> if the popup should be automatically hidden
     * when the history token changes, such as when the user presses the
     * browser's back button.
     * 
     * @return true if enabled, false if disabled
     */
    public boolean isAutoHideOnHistoryEventsEnabled() {
        return autoHideOnHistoryEvents;
    }

    /**
     * Returns <code>true</code> if a glass element will be displayed under the
     * {@link RelativePopupPanel}.
     * 
     * @return true if enabled
     */
    public boolean isGlassEnabled() {
        return isGlassEnabled;
    }

    public boolean isHideOnEscape() {
        return this.hideOnEscape;
    }

    /**
     * Returns <code>true</code> if keyboard or mouse events that do not target
     * the RelativePopupPanel or its children should be ignored.
     * 
     * @return true if popup is modal, false if not
     */
    public boolean isModal() {
        return modal;
    }

    /**
     * Returns <code>true</code> if the popup should preview all native events,
     * even if the event has already been consumed by another popup.
     * 
     * @return true if previewAllNativeEvents is enabled, false if disabled
     */
    public boolean isPreviewingAllNativeEvents() {
        return previewAllNativeEvents;
    }

    /**
     * Determines whether or not this popup is showing.
     * 
     * @return <code>true</code> if the popup is showing
     * @see #show()
     * @see #hide()
     */
    public boolean isShowing() {
        return showing;
    }

    /**
     * Determines whether or not this popup is visible. Note that this just
     * checks the <code>visibility</code> style attribute, which is set in the
     * {@link #setVisible(boolean)} method. If you want to know if the popup is
     * attached to the page, use {@link #isShowing()} instead.
     * 
     * @return <code>true</code> if the object is visible
     * @see #setVisible(boolean)
     */
    @Override
    public boolean isVisible() {
        return !"hidden".equals(getElement().getStyle().getProperty("visibility"));
    }

    /**
     * @deprecated Use {@link #onPreviewNativeEvent} instead
     */
    @Deprecated
    public boolean onEventPreview(Event event) {
        return true;
    }

    /**
     * Popups get an opportunity to preview keyboard events before they are
     * passed to a widget contained by the Popup.
     * 
     * @param key
     *            the key code of the depressed key
     * @param modifiers
     *            keyboard modifiers, as specified in
     *            {@link com.google.gwt.event.dom.client.KeyCodes}.
     * @return <code>false</code> to suppress the event
     * @deprecated Use {@link #onPreviewNativeEvent} instead
     */
    @Deprecated
    public boolean onKeyDownPreview(char key, int modifiers) {
        return true;
    }

    /**
     * Popups get an opportunity to preview keyboard events before they are
     * passed to a widget contained by the Popup.
     * 
     * @param key
     *            the unicode character pressed
     * @param modifiers
     *            keyboard modifiers, as specified in
     *            {@link com.google.gwt.event.dom.client.KeyCodes}.
     * @return <code>false</code> to suppress the event
     * @deprecated Use {@link #onPreviewNativeEvent} instead
     */
    @Deprecated
    public boolean onKeyPressPreview(char key, int modifiers) {
        return true;
    }

    /**
     * Popups get an opportunity to preview keyboard events before they are
     * passed to a widget contained by the Popup.
     * 
     * @param key
     *            the key code of the released key
     * @param modifiers
     *            keyboard modifiers, as specified in
     *            {@link com.google.gwt.event.dom.client.KeyCodes}.
     * @return <code>false</code> to suppress the event
     * @deprecated Use {@link #onPreviewNativeEvent} instead
     */
    @Deprecated
    public boolean onKeyUpPreview(char key, int modifiers) {
        return true;
    }

    /**
     * Remove an autoHide partner.
     * 
     * @param partner
     *            the auto hide partner to remove
     */
    public void removeAutoHidePartner(Element partner) {
        assert partner != null : "partner cannot be null";
        if (autoHidePartners != null) {
            autoHidePartners.remove(partner);
        }
    }

    public void setAnimationEnabled(boolean enable) {
        isAnimationEnabled = enable;
    }

    /**
     * Enable or disable the autoHide feature. When enabled, the popup will be
     * automatically hidden when the user clicks outside of it.
     * 
     * @param autoHide
     *            true to enable autoHide, false to disable
     */
    public void setAutoHideEnabled(boolean autoHide) {
        this.autoHide = autoHide;
    }

    /**
     * Enable or disable autoHide on history change events. When enabled, the
     * popup will be automatically hidden when the history token changes, such
     * as when the user presses the browser's back button. Disabled by default.
     * 
     * @param enabled
     *            true to enable, false to disable
     */
    public void setAutoHideOnHistoryEventsEnabled(boolean enabled) {
        this.autoHideOnHistoryEvents = enabled;
    }

    /**
     * When enabled, the background will be blocked with a semi-transparent pane
     * the next time it is shown. If the RelativePopupPanel is already visible,
     * the glass will not be displayed until it is hidden and shown again.
     * 
     * @param enabled
     *            true to enable, false to disable
     */
    public void setGlassEnabled(boolean enabled) {
        this.isGlassEnabled = enabled;
        if (enabled && glass == null) {
            glass = Document.get().createDivElement();
            glass.setClassName(glassStyleName);
            glass.getStyle().setPosition(Position.ABSOLUTE);
            glass.getStyle().setLeft(0, Unit.PX);
            glass.getStyle().setTop(0, Unit.PX);
        }
    }

    /**
     * Sets the style name to be used on the glass element. By default, this is
     * "gwt-PopupPanelGlass".
     * 
     * @param glassStyleName
     *            the glass element's style name
     */
    public void setGlassStyleName(String glassStyleName) {
        this.glassStyleName = glassStyleName;
        if (glass != null) {
            glass.setClassName(glassStyleName);
        }
    }

    /**
     * Sets the height of the panel's child widget. If the panel's child widget
     * has not been set, the height passed in will be cached and used to set the
     * height immediately after the child widget is set.
     * 
     * <p>
     * Note that subclasses may have a different behavior. A subclass may decide
     * not to change the height of the child widget. It may instead decide to
     * change the height of an internal panel widget, which contains the child
     * widget.
     * </p>
     * 
     * @param height
     *            the object's new height, in CSS units (e.g. "10px", "1em")
     */
    @Override
    public void setHeight(String height) {
        desiredHeight = height;
        maybeUpdateSize();
        // If the user cleared the size, revert to not trying to control
        // children.
        if (height.length() == 0) {
            desiredHeight = null;
        }
    }

    public void setHideOnEscape(boolean hideOnEscape) {
        this.hideOnEscape = hideOnEscape;
    }

    /**
     * When the popup is modal, keyboard or mouse events that do not target the
     * RelativePopupPanel or its children will be ignored.
     * 
     * @param modal
     *            true to make the popup modal
     */
    public void setModal(boolean modal) {
        this.modal = modal;
    }

    /**
     * Sets the popup's position relative to the browser's client area. The
     * popup's position may be set before calling {@link #show()}.
     * 
     * @param left
     *            the left position, in pixels
     * @param top
     *            the top position, in pixels
     */
    public void setPopupPosition(int left, int top) {
        // Save the position of the popup
        leftPosition = left;
        topPosition = top;
        // Account for the difference between absolute position and the
        // body's positioning context.
        left -= Document.get().getBodyOffsetLeft();
        top -= Document.get().getBodyOffsetTop();
        // Set the popup's position manually, allowing setPopupPosition() to be
        // called before show() is called (so a popup can be positioned without
        // it
        // 'jumping' on the screen).
        Element elem = getElement();
        elem.getStyle().setPropertyPx("left", left);
        elem.getStyle().setPropertyPx("top", top);
    }

    /**
     * Sets the popup's position using a {@link PositionCallback}, and shows the
     * popup. The callback allows positioning to be performed based on the
     * offsetWidth and offsetHeight of the popup, which are normally not
     * available until the popup is showing. By positioning the popup before it
     * is shown, the the popup will not jump from its original position to the
     * new position.
     * 
     * @param callback
     *            the callback to set the position of the popup
     * @see PositionCallback#setPosition(int offsetWidth, int offsetHeight)
     */
    public void setPopupPositionAndShow(PositionCallback callback) {
        setVisible(false);
        show();
        callback.setPosition(getOffsetWidth(), getOffsetHeight());
        setVisible(true);
    }

    public void setPositioningContainer(ComplexPanel positioningContainer) {
        this.positioningContainer = positioningContainer;
    }

    /**
     * <p>
     * When enabled, the popup will preview all native events, even if another
     * popup was opened after this one.
     * </p>
     * <p>
     * If autoHide is enabled, enabling this feature will cause the popup to
     * autoHide even if another non-modal popup was shown after it. If this
     * feature is disabled, the popup will only autoHide if it was the last
     * popup opened.
     * </p>
     * 
     * @param previewAllNativeEvents
     *            true to enable, false to disable
     */
    public void setPreviewingAllNativeEvents(boolean previewAllNativeEvents) {
        this.previewAllNativeEvents = previewAllNativeEvents;
    }

    @Override
    public void setTitle(String title) {
        Element containerElement = getContainerElement();
        if (title == null || title.length() == 0) {
            containerElement.removeAttribute("title");
        } else {
            containerElement.setAttribute("title", title);
        }
    }

    /**
     * Sets whether this object is visible. This method just sets the
     * <code>visibility</code> style attribute. You need to call {@link #show()}
     * to actually attached/detach the {@link RelativePopupPanel} to the page.
     * 
     * @param visible
     *            <code>true</code> to show the object, <code>false</code> to
     *            hide it
     * @see #show()
     * @see #hide()
     */
    @Override
    public void setVisible(boolean visible) {
        // We use visibility here instead of UIObject's default of display
        // Because the panel is absolutely positioned, this will not create
        // "holes" in displayed contents and it allows normal layout passes
        // to occur so the size of the RelativePopupPanel can be reliably
        // determined.
        DOM.setStyleAttribute(getElement(), "visibility", visible ? "visible" : "hidden");
    }

    @Override
    public void setWidget(Widget w) {
        super.setWidget(w);
        maybeUpdateSize();
    }

    /**
     * Sets the width of the panel's child widget. If the panel's child widget
     * has not been set, the width passed in will be cached and used to set the
     * width immediately after the child widget is set.
     * 
     * <p>
     * Note that subclasses may have a different behavior. A subclass may decide
     * not to change the width of the child widget. It may instead decide to
     * change the width of an internal panel widget, which contains the child
     * widget.
     * </p>
     * 
     * @param width
     *            the object's new width, in CSS units (e.g. "10px", "1em")
     */
    @Override
    public void setWidth(String width) {
        desiredWidth = width;
        maybeUpdateSize();
        // If the user cleared the size, revert to not trying to control
        // children.
        if (width.length() == 0) {
            desiredWidth = null;
        }
    }

    /**
     * Shows the popup and attach it to the page. It must have a child widget
     * before this method is called.
     */
    public void show() {
        if (showing) {
            return;
        }
        setState(true, true);
    }

    /**
     * Normally, the popup is positioned directly below the relative target,
     * with its left edge aligned with the left edge of the target. Depending on
     * the width and height of the popup and the distance from the target to the
     * bottom and right edges of the window, the popup may be displayed directly
     * above the target, and/or its right edge may be aligned with the right
     * edge of the target.
     * 
     * @param target
     *            the target to show the popup below
     */
    public final void showRelativeTo(final UIObject target) {
        // Set the position of the popup right before it is shown.
        setPopupPositionAndShow(new PositionCallback() {
            public void setPosition(int offsetWidth, int offsetHeight) {
                position(target, offsetWidth, offsetHeight);
            }
        });
    }

    /**
     * Remove focus from an Element.
     * 
     * @param elt
     *            The Element on which <code>blur()</code> will be invoked
     */
    private native void blur(Element elt) /*-{
                                          // Issue 2390: blurring the body causes IE to disappear to the background
                                          if (elt.blur && elt != $doc.body) {
                                          elt.blur();
                                          }
                                          }-*/;

    /**
     * Does the event target one of the partner elements?
     * 
     * @param event
     *            the native event
     * @return true if the event targets a partner
     */
    private boolean eventTargetsPartner(NativeEvent event) {
        if (autoHidePartners == null) {
            return false;
        }
        EventTarget target = event.getEventTarget();
        if (Element.is(target)) {
            for (Element elem : autoHidePartners) {
                if (elem.isOrHasChild(Element.as(target))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Does the event target this popup?
     * 
     * @param event
     *            the native event
     * @return true if the event targets the popup
     */
    private boolean eventTargetsPopup(NativeEvent event) {
        EventTarget target = event.getEventTarget();
        if (Element.is(target)) {
            Element eTarget = Element.as(target);
            return getElement().isOrHasChild(eTarget);
        }
        return false;
    }

    /**
     * Get the element that {@link PopupImpl} uses. PopupImpl creates an element
     * that goes inside of the outer element, so all methods in PopupImpl are
     * relative to the first child of the outer element, not the outer element
     * itself.
     * 
     * @return the Element that {@link PopupImpl} creates and expects
     */
    private Element getPopupImplElement() {
        return DOM.getFirstChild(super.getContainerElement());
    }

    /**
     * Positions the popup, called after the offset width and height of the
     * popup are known.
     * 
     * @param relativeObject
     *            the ui object to position relative to
     * @param offsetWidth
     *            the drop down's offset width
     * @param offsetHeight
     *            the drop down's offset height
     */
    private void position(final UIObject relativeObject, int offsetWidth, int offsetHeight) {
        // Calculate left position for the popup. The computation for
        // the left position is bidi-sensitive.
        int textBoxOffsetWidth = relativeObject.getOffsetWidth();
        // Compute the difference between the popup's width and the
        // textbox's width
        int offsetWidthDiff = offsetWidth - textBoxOffsetWidth;
        int left;
        if (LocaleInfo.getCurrentLocale().isRTL()) { // RTL case
            int textBoxAbsoluteLeft = relativeObject.getAbsoluteLeft();
            // Right-align the popup. Note that this computation is
            // valid in the case where offsetWidthDiff is negative.
            left = textBoxAbsoluteLeft - offsetWidthDiff;
            // If the suggestion popup is not as wide as the text box, always
            // align to the right edge of the text box. Otherwise, figure out
            // whether
            // to right-align or left-align the popup.
            if (offsetWidthDiff > 0) {
                // Make sure scrolling is taken into account, since
                // box.getAbsoluteLeft() takes scrolling into account.
                int windowRight = Window.getClientWidth() + Window.getScrollLeft();
                int windowLeft = Window.getScrollLeft();
                // Compute the left value for the right edge of the textbox
                int textBoxLeftValForRightEdge = textBoxAbsoluteLeft + textBoxOffsetWidth;
                // Distance from the right edge of the text box to the right
                // edge
                // of the window
                int distanceToWindowRight = windowRight - textBoxLeftValForRightEdge;
                // Distance from the right edge of the text box to the left edge
                // of the
                // window
                int distanceFromWindowLeft = textBoxLeftValForRightEdge - windowLeft;
                // If there is not enough space for the overflow of the popup's
                // width to the right of the text box and there IS enough space
                // for the
                // overflow to the right of the text box, then left-align the
                // popup.
                // However, if there is not enough space on either side, stick
                // with
                // right-alignment.
                if (distanceFromWindowLeft < offsetWidth && distanceToWindowRight >= offsetWidthDiff) {
                    // Align with the left edge of the text box.
                    left = textBoxAbsoluteLeft;
                }
            }
        } else { // LTR case
            // Left-align the popup.
            left = relativeObject.getAbsoluteLeft();
            // If the suggestion popup is not as wide as the text box, always
            // align to
            // the left edge of the text box. Otherwise, figure out whether to
            // left-align or right-align the popup.
            if (offsetWidthDiff > 0) {
                // Make sure scrolling is taken into account, since
                // box.getAbsoluteLeft() takes scrolling into account.
                int windowRight = Window.getClientWidth() + Window.getScrollLeft();
                int windowLeft = Window.getScrollLeft();
                // Distance from the left edge of the text box to the right edge
                // of the window
                int distanceToWindowRight = windowRight - left;
                // Distance from the left edge of the text box to the left edge
                // of the
                // window
                int distanceFromWindowLeft = left - windowLeft;
                // If there is not enough space for the overflow of the popup's
                // width to the right of hte text box, and there IS enough space
                // for the
                // overflow to the left of the text box, then right-align the
                // popup.
                // However, if there is not enough space on either side, then
                // stick with
                // left-alignment.
                if (distanceToWindowRight < offsetWidth && distanceFromWindowLeft >= offsetWidthDiff) {
                    // Align with the right edge of the text box.
                    left -= offsetWidthDiff;
                }
            }
        }
        // Calculate top position for the popup
        int top = relativeObject.getAbsoluteTop();
        // Make sure scrolling is taken into account, since
        // box.getAbsoluteTop() takes scrolling into account.
        int windowTop = Window.getScrollTop();
        int windowBottom = Window.getScrollTop() + Window.getClientHeight();
        // Distance from the top edge of the window to the top edge of the
        // text box
        int distanceFromWindowTop = top - windowTop;
        // Distance from the bottom edge of the window to the bottom edge of
        // the text box
        int distanceToWindowBottom = windowBottom - (top + relativeObject.getOffsetHeight());
        // If there is not enough space for the popup's height below the text
        // box and there IS enough space for the popup's height above the text
        // box, then then position the popup above the text box. However, if
        // there
        // is not enough space on either side, then stick with displaying the
        // popup below the text box.
        if (distanceToWindowBottom < offsetHeight && distanceFromWindowTop >= offsetHeight) {
            top -= offsetHeight;
        } else {
            // Position above the text box
            top += relativeObject.getOffsetHeight();
        }
        setPopupPosition(left, top);
    }

    /**
     * Preview the {@link NativePreviewEvent}.
     * 
     * @param event
     *            the {@link NativePreviewEvent}
     */
    private void previewNativeEvent(NativePreviewEvent event) {
        // If the event has been canceled or consumed, ignore it
        if (event.isCanceled() || (!previewAllNativeEvents && event.isConsumed())) {
            // We need to ensure that we cancel the event even if its been
            // consumed so
            // that popups lower on the stack do not auto hide
            if (modal) {
                event.cancel();
            }
            return;
        }
        // Fire the event hook and return if the event is canceled
        onPreviewNativeEvent(event);
        if (event.isCanceled()) {
            return;
        }
        // If the event targets the popup or the partner, consume it
        Event nativeEvent = Event.as(event.getNativeEvent());
        boolean eventTargetsPopupOrPartner = eventTargetsPopup(nativeEvent) || eventTargetsPartner(nativeEvent);
        if (eventTargetsPopupOrPartner) {
            event.consume();
        }
        // Cancel the event if it doesn't target the modal popup. Note that the
        // event can be both canceled and consumed.
        if (modal) {
            switch (nativeEvent.getType()) {
            case BrowserEvents.KEYDOWN:
            case BrowserEvents.KEYPRESS:
            case BrowserEvents.KEYUP:
                if (nativeEvent.getAltKey() || nativeEvent.getMetaKey() || nativeEvent.getCtrlKey()) {
                    return;
                }
            }
            event.cancel();
        }
        // Switch on the event type
        int type = nativeEvent.getTypeInt();
        switch (type) {
        case Event.ONMOUSEDOWN:
        case Event.ONTOUCHSTART:
            // Don't eat events if event capture is enabled, as this can
            // interfere with dialog dragging, for example.
            if (DOM.getCaptureElement() != null) {
                event.consume();
                return;
            }
            if (!eventTargetsPopupOrPartner && autoHide) {
                EventTarget target = nativeEvent.getEventTarget();
                // if (Element.is(target)) {
                // ClientUtils.dumpElementTree(Element.as(target));
                // }
                hide(true);
                return;
            }
            break;
        case Event.ONMOUSEUP:
        case Event.ONMOUSEMOVE:
        case Event.ONCLICK:
        case Event.ONDBLCLICK: {
            // Don't eat events if event capture is enabled, as this can
            // interfere with dialog dragging, for example.
            if (DOM.getCaptureElement() != null) {
                event.consume();
                return;
            }
            break;
        }
        case Event.ONFOCUS: {
            if (nativeEvent.getEventTarget().is(Element.class)) {
                Element target = nativeEvent.getEventTarget().cast();
                if (modal && !eventTargetsPopupOrPartner && (target != null)) {
                    blur(target);
                    event.cancel();
                    return;
                }
            }
            break;
        }
        case Event.ONKEYPRESS: {
            if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE && isHideOnEscape()) {
                hide(false);
                event.cancel();
                return;
            }
            break;
        }
        }
    }

    /**
     * Set the showing state of the popup. If maybeAnimate is true, the
     * animation will be used to set the state. If it is false, the animation
     * will be cancelled.
     * 
     * @param showing
     *            the new state
     * @param maybeAnimate
     *            true to possibly run the animation
     */
    private void setState(boolean showing, boolean maybeAnimate) {
        if (maybeAnimate) {
            resizeAnimation.setState(showing);
        } else {
            resizeAnimation.cancel();
        }
        this.showing = showing;
        // Create or remove the native preview handler
        if (showing) {
            nativePreviewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
                public void onPreviewNativeEvent(NativePreviewEvent event) {
                    previewNativeEvent(event);
                }
            });
            historyHandlerRegistration = History.addValueChangeHandler(new ValueChangeHandler<String>() {
                public void onValueChange(ValueChangeEvent<String> event) {
                    if (autoHideOnHistoryEvents) {
                        hide();
                    }
                }
            });
        } else {
            if (nativePreviewHandlerRegistration != null) {
                nativePreviewHandlerRegistration.removeHandler();
                nativePreviewHandlerRegistration = null;
            }
            if (historyHandlerRegistration != null) {
                historyHandlerRegistration.removeHandler();
                historyHandlerRegistration = null;
            }
        }
    }

    @Override
    protected Element getContainerElement() {
        return impl.getContainerElement(getPopupImplElement()).cast();
    }

    /**
     * Get the glass element used by this {@link RelativePopupPanel}. The
     * element is not created until it is enabled via
     * {@link #setGlassEnabled(boolean)}.
     * 
     * @return the glass element, or null if not created
     */
    protected Element getGlassElement() {
        return glass;
    }

    @Override
    protected Element getStyleElement() {
        return impl.getStyleElement(getPopupImplElement()).cast();
    }

    protected void onPreviewNativeEvent(NativePreviewEvent event) {
        // Cancel the event based on the deprecated onEventPreview() method
        if (event.isFirstHandler() && !onEventPreview(Event.as(event.getNativeEvent()))) {
            event.cancel();
        }
    }

    @Override
    protected void onUnload() {
        // Just to be sure, we perform cleanup when the popup is unloaded (i.e.
        // removed from the DOM). This is normally taken care of in hide(), but
        // it
        // can be missed if someone removes the popup directly from the
        // RootPanel.
        if (isShowing()) {
            setState(false, false);
        }
    }

    /**
     * We control size by setting our child widget's size. However, if we don't
     * currently have a child, we record the size the user wanted so that when
     * we do get a child, we can set it correctly. Until size is explicitly
     * cleared, any child put into the popup will be given that size.
     */
    void maybeUpdateSize() {
        // For subclasses of RelativePopupPanel, we want the default behavior of
        // setWidth
        // and setHeight to change the dimensions of RelativePopupPanel's child
        // widget.
        // We do this because RelativePopupPanel's child widget is the first
        // widget in
        // the hierarchy which provides structure to the panel. DialogBox is
        // an example of this. We want to set the dimensions on DialogBox's
        // FlexTable, which is RelativePopupPanel's child widget. However, it is
        // not
        // DialogBox's child widget. To make sure that we are actually getting
        // RelativePopupPanel's child widget, we have to use super.getWidget().
        Widget w = super.getWidget();
        if (w != null) {
            if (desiredHeight != null) {
                w.setHeight(desiredHeight);
            }
            if (desiredWidth != null) {
                w.setWidth(desiredWidth);
            }
        }
    }

    /**
     * Sets the animation used to animate this popup. Used by gwt-incubator to
     * allow DropDownPanel to override the default popup animation. Not
     * protected because the exact API may change in gwt 1.6.
     * 
     * @param animation
     *            the animation to use for this popup
     */
    void setAnimation(ResizeAnimation animation) {
        resizeAnimation = animation;
    }

    /**
     * Enable or disable animation of the {@link RelativePopupPanel}.
     * 
     * @param type
     *            the type of animation to use
     */
    void setAnimationType(AnimationType type) {
        animType = type;
    }

    /**
     * A callback that is used to set the position of a
     * {@link RelativePopupPanel} right before it is shown.
     */
    public interface PositionCallback {
        /**
         * Provides the opportunity to set the position of the
         * RelativePopupPanel right before the RelativePopupPanel is shown. The
         * offsetWidth and offsetHeight values of the RelativePopupPanel are
         * made available to allow for positioning based on its size.
         * 
         * @param offsetWidth
         *            the offsetWidth of the RelativePopupPanel
         * @param offsetHeight
         *            the offsetHeight of the RelativePopupPanel
         * @see RelativePopupPanel#setPopupPositionAndShow(PositionCallback)
         */
        void setPosition(int offsetWidth, int offsetHeight);
    }

    public static class RelativePopupPanelFilter implements CollectionFilter<Widget> {
        @Override
        public boolean allow(Widget o) {
            return o instanceof RelativePopupPanel;
        }
    }

    /**
     * The type of animation to use when opening the popup.
     * 
     * <ul>
     * <li>CENTER - Expand from the center of the popup</li>
     * <li>ONE_WAY_CORNER - Expand from the top left corner, do not animate
     * hiding</li>
     * </ul>
     */
    static enum AnimationType {
        CENTER, ONE_WAY_CORNER, ROLL_DOWN
    }

    /**
     * An {@link Animation} used to enlarge the popup into view.
     */
    static class ResizeAnimation extends Animation {
        /**
         * The {@link RelativePopupPanel} being affected.
         */
        private RelativePopupPanel curPanel = null;

        /**
         * The offset height and width of the current {@link RelativePopupPanel}
         * .
         */
        private int offsetHeight, offsetWidth = -1;

        /**
         * A boolean indicating whether we are showing or hiding the popup.
         */
        private boolean showing;

        /**
         * A boolean indicating whether the glass element is currently attached.
         */
        private boolean glassShowing;

        private HandlerRegistration resizeRegistration;

        /**
         * Create a new {@link ResizeAnimation}.
         * 
         * @param panel
         *            the panel to affect
         */
        public ResizeAnimation(RelativePopupPanel panel) {
            this.curPanel = panel;
        }

        /**
         * Open or close the content. This method always called immediately
         * after the RelativePopupPanel showing state has changed, so we base
         * the animation on the current state.
         * 
         * @param showing
         *            true if the popup is showing, false if not
         */
        public void setState(boolean showing) {
            // Immediately complete previous open/close animation
            cancel();
            // Determine if we need to animate
            boolean animate = curPanel.isAnimationEnabled;
            if (curPanel.animType != AnimationType.CENTER && !showing) {
                animate = false;
            }
            // Open the new item
            this.showing = showing;
            if (animate) {
                // impl.onShow takes some time to complete, so we do it before
                // starting
                // the animation. If we move this to onStart, the animation will
                // look
                // choppy or not run at all.
                if (showing) {
                    maybeShowGlass();
                    // Set the position attribute, and then attach to the DOM.
                    // Otherwise,
                    // the RelativePopupPanel will appear to 'jump' from its
                    // static/relative
                    // position to its absolute position (issue #1231).
                    DOM.setStyleAttribute(curPanel.getElement(), "position", "absolute");
                    if (curPanel.topPosition != -1) {
                        curPanel.setPopupPosition(curPanel.leftPosition, curPanel.topPosition);
                    }
                    impl.setClip(curPanel.getElement(), getRectString(0, 0, 0, 0));
                    curPanel.getPositioningContainer().add(curPanel);
                }
                // Wait for the popup panel to be attached before running the
                // animation
                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
                    public void execute() {
                        run(ANIMATION_DURATION);
                    }
                });
            } else {
                onInstantaneousRun();
            }
        }

        /**
         * @return a rect string
         */
        private String getRectString(int top, int right, int bottom, int left) {
            return "rect(" + top + "px, " + right + "px, " + bottom + "px, " + left + "px)";
        }

        /**
         * Show or hide the glass.
         */
        private void maybeShowGlass() {
            if (showing) {
                if (curPanel.isGlassEnabled) {
                    Document.get().getBody().appendChild(curPanel.glass);
                    resizeRegistration = Window.addResizeHandler(curPanel.glassResizer);
                    curPanel.glassResizer.onResize(null);
                    glassShowing = true;
                }
            } else if (glassShowing) {
                Document.get().getBody().removeChild(curPanel.glass);
                resizeRegistration.removeHandler();
                resizeRegistration = null;
                glassShowing = false;
            }
        }

        private void onInstantaneousRun() {
            maybeShowGlass();
            if (showing) {
                // Set the position attribute, and then attach to the DOM.
                // Otherwise,
                // the RelativePopupPanel will appear to 'jump' from its
                // static/relative
                // position to its absolute position (issue #1231).
                DOM.setStyleAttribute(curPanel.getElement(), "position", "absolute");
                if (curPanel.topPosition != -1) {
                    curPanel.setPopupPosition(curPanel.leftPosition, curPanel.topPosition);
                }
                curPanel.getPositioningContainer().add(curPanel);
            } else {
                curPanel.getPositioningContainer().remove(curPanel);
            }
            DOM.setStyleAttribute(curPanel.getElement(), "overflow", "visible");
        }

        @Override
        protected void onComplete() {
            if (!showing) {
                maybeShowGlass();
                curPanel.getPositioningContainer().remove(curPanel);
            }
            impl.setClip(curPanel.getElement(), "rect(auto, auto, auto, auto)");
            DOM.setStyleAttribute(curPanel.getElement(), "overflow", "visible");
        }

        @Override
        protected void onStart() {
            offsetHeight = curPanel.getOffsetHeight();
            offsetWidth = curPanel.getOffsetWidth();
            DOM.setStyleAttribute(curPanel.getElement(), "overflow", "hidden");
            super.onStart();
        }

        @Override
        protected void onUpdate(double progress) {
            if (!showing) {
                progress = 1.0 - progress;
            }
            // Determine the clipping size
            int top = 0;
            int left = 0;
            int right = 0;
            int bottom = 0;
            int height = (int) (progress * offsetHeight);
            int width = (int) (progress * offsetWidth);
            switch (curPanel.animType) {
            case ROLL_DOWN:
                right = offsetWidth;
                bottom = height;
                break;
            case CENTER:
                top = (offsetHeight - height) >> 1;
                left = (offsetWidth - width) >> 1;
                right = left + width;
                bottom = top + height;
                break;
            case ONE_WAY_CORNER:
                if (LocaleInfo.getCurrentLocale().isRTL()) {
                    left = offsetWidth - width;
                }
                right = left + width;
                bottom = top + height;
                break;
            }
            // Set the rect clipping
            impl.setClip(curPanel.getElement(), getRectString(top, right, bottom, left));
        }
    }
}