/*
* Copyright 2014 cruxframework.org.
*
* 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 org.cruxframework.crux.smartfaces.client.dialog;
import java.util.ArrayList;
import java.util.List;
import org.cruxframework.crux.core.client.collection.FastList;
import org.cruxframework.crux.core.client.css.animation.Animation;
import org.cruxframework.crux.smartfaces.client.backbone.common.FacesBackboneResourcesCommon;
import org.cruxframework.crux.smartfaces.client.dialog.animation.HasDialogAnimation;
import org.cruxframework.crux.smartfaces.client.util.animation.InOutAnimation;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.BodyElement;
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.Visibility;
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.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
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.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
/**
* A panel that can "pop up" over other widgets. It overlays the browser's
* client area (and any previously-created popups).
*
* <p>
* A PopupPanel should not generally be added to other panels; rather, it should
* be shown and hidden using the {@link #show()} and {@link #hide()} methods.
* </p>
*
* @author Thiago da Rosa de Bustamante
*/
public class PopupPanel extends SimplePanel implements HasDialogAnimation, HasCloseHandlers<PopupPanel>, HasOpenHandlers<PopupPanel>, NativePreviewHandler
{
public static final String DEFAULT_GLASS_STYLE_NAME = "faces-overlay";
private static List<CloseHandler<PopupPanel>> defaultCloseHandlers = new ArrayList<CloseHandler<PopupPanel>>();
private static List<OpenHandler<PopupPanel>> defaultOpenHandlers = new ArrayList<OpenHandler<PopupPanel>>();
private static final String FACES_POPUP_CONTENT = "faces-popup-content";
/**
* Coordinates the times that the glass is called.
*/
private static int numberCalls = 0;
private static final String STYLE_POPUP = "faces-popup";
private boolean animating;
private InOutAnimation animation;
private double animationDuration = -1;
private boolean animationEnabled;
private boolean autoHide;
private boolean autoHideOnHistoryEvents;
private FastList<Element> autoHidePartners;
private Element containerElement;
private Element glass;
private boolean glassShowing;
private String glassStyleName = DEFAULT_GLASS_STYLE_NAME;
private HandlerRegistration historyHandlerRegistration;
private int left = -1;
private boolean modal;
private HandlerRegistration nativePreviewHandlerRegistration;
private PopupCentralizer popupCentralizer = GWT.create(PopupCentralizer.class);
private boolean showing;
private int top = -1;
/**
* Creates an empty popup panel. A child widget must be added to it before
* it is shown.
*/
public PopupPanel()
{
this(false);
}
/**
* 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 PopupPanel(boolean autoHide)
{
this(autoHide, false);
}
/**
* 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 PopupPanel or its children should be ignored
*/
public PopupPanel(boolean autoHide, boolean modal)
{
FacesBackboneResourcesCommon.INSTANCE.css().ensureInjected();
this.autoHide = autoHide;
this.autoHideOnHistoryEvents = autoHide;
this.modal = modal;
if (modal)
{
glass = Document.get().createDivElement();
glass.setClassName(glassStyleName);
glass.addClassName(FacesBackboneResourcesCommon.INSTANCE.css().facesOverlay());
}
addCloseHandler(new CloseHandler<PopupPanel>()
{
@Override
public void onClose(CloseEvent<PopupPanel> event)
{
if (defaultCloseHandlers != null)
{
for (CloseHandler<PopupPanel> closeHandler : defaultCloseHandlers)
{
closeHandler.onClose(event);
}
}
}
});
addOpenHandler(new OpenHandler<PopupPanel>()
{
@Override
public void onOpen(OpenEvent<PopupPanel> event)
{
if (defaultOpenHandlers != null)
{
for (OpenHandler<PopupPanel> openHandler : defaultOpenHandlers)
{
openHandler.onOpen(event);
}
}
}
});
containerElement = Document.get().createDivElement().cast();
super.getContainerElement().appendChild(containerElement);
setPosition(0, 0);
setStyleName(getContainerElement(), FACES_POPUP_CONTENT);
}
/**
* 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 FastList<Element>();
}
autoHidePartners.add(partner);
}
@Override
public HandlerRegistration addCloseHandler(CloseHandler<PopupPanel> handler)
{
return addHandler(handler, CloseEvent.getType());
}
@Override
public HandlerRegistration addOpenHandler(OpenHandler<PopupPanel> handler)
{
return addHandler(handler, OpenEvent.getType());
}
/**
* Centers the popup in the browser window and shows it. If the popup was
* already showing, then it is centered.
*/
public void center()
{
if (!popupCentralizer.isCentralized())
{
if (animating)
{
fixPositionToCenter();
Scheduler.get().scheduleFixedPeriod(new RepeatingCommand()
{
@Override
public boolean execute()
{
if (animating)
{
return true;
}
centralizeMe();
return false;
}
}, 10);
}
else
{
centralizeMe();
if (!isShowing())
{
show();
}
}
}
}
/**
* Gets the style name to be used on the glass element.
*
* @return the glass element's style name
*/
public String getGlassStyleName()
{
return glassStyleName;
}
/**
* Hides the popup and detaches it from the page. This has no effect if it
* is not currently showing.
*/
public void hide()
{
hide(false);
}
/**
* Determines if the popup is animating or not
*
* @return <code>true</code> if the popup is running a animation
*/
public boolean isAnimating()
{
return animating;
}
@Override
public boolean isAnimationEnabled()
{
return animationEnabled;
}
/**
* 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 the popup opens a modal window
* @return true if modal
*/
public boolean isModal()
{
return modal;
}
/**
* 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 !getElement().getStyle().getVisibility().equals(Visibility.HIDDEN.getCssName());
}
@Override
public void onPreviewNativeEvent(NativePreviewEvent event)
{
// If the event has been canceled or consumed, ignore it
if (event.isCanceled() || (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);
// Cancel the event based on the deprecated onEventPreview() method
// if (event.isFirstHandler()
// && !onEventPreview(Event.as(event.getNativeEvent()))) {
// event.cancel();
// }
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)
{
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)
{
hide(true);
return;
}
break;
case Event.ONMOUSEUP:
case Event.ONMOUSEMOVE:
case Event.ONCLICK:
case Event.ONDBLCLICK:
case Event.ONTOUCHEND:
{
// 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:
{
@SuppressWarnings("deprecation")
Element target = nativeEvent.getTarget();
if (modal && !eventTargetsPopupOrPartner && (target != null)) {
blur(target);
event.cancel();
return;
}
break;
}
}
}
/**
* 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);
}
}
/**
* Defines the animation used to animate popup entrances and exits
* @param animation
*/
public void setAnimation(InOutAnimation animation)
{
this.animation = animation;
setAnimationEnabled(animation != null);
}
/**
* Defines the duration for the animation used to animate popup entrances and exits
* @param duration animatin duration in seconds.
*/
public void setAnimationDuration(double duration)
{
this.animationDuration = duration;
}
@Override
public void setAnimationEnabled(boolean enable)
{
animationEnabled = 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;
}
/**
* Sets the style name to be used on the glass element.
*
* @param glassStyleName
* the glass element's style name
*/
public void setGlassStyleName(String glassStyleName)
{
this.glassStyleName = glassStyleName;
if (glass != null)
{
glass.setClassName(glassStyleName);
glass.addClassName(FacesBackboneResourcesCommon.INSTANCE.css().facesOverlay());
}
}
/**
* 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 setPosition(int left, int top)
{
if (popupCentralizer.isCentralized())
{
uncentralizeMe();
}
// Account for the difference between absolute position and the
// body's positioning context.
Document document = Document.get();
left -= document.getBodyOffsetLeft();
top -= document.getBodyOffsetTop();
this.left = left;
this.top = top;
setPopupPositionStyle(left, top);
}
@Override
public void setStyleName(String style)
{
super.setStyleName(style);
addStyleName(STYLE_POPUP);
addStyleName(FacesBackboneResourcesCommon.INSTANCE.css().facesPopup());
}
@Override
public void setStyleName(String style, boolean add)
{
super.setStyleName(style, add);
if (!add)
{
addStyleName(STYLE_POPUP);
addStyleName(FacesBackboneResourcesCommon.INSTANCE.css().facesPopup());
}
}
/**
* 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 PopupPanel} 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 PopupPanel can be reliably determined.
getElement().getStyle().setVisibility(visible?Visibility.VISIBLE:Visibility.HIDDEN);
if (glass != null)
{
glass.getStyle().setVisibility(visible?Visibility.VISIBLE:Visibility.HIDDEN);
}
}
/**
* Shows the popup and attach it to the page. It must have a child widget
* before this method is called.
*/
public void show()
{
doShow(isAnimationEnabled());
}
/**
* 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)
{
setVisible(false);
doShow(false);
setPosition(getLeftRelativeObject(target), getTopRelativeObject(target));
setVisible(true);
if (isAnimationEnabled())
{
runEntranceAnimation(null);
}
}
@Override
@SuppressWarnings("deprecation")
protected com.google.gwt.user.client.Element getContainerElement()
{
return containerElement.cast();
}
/**
* Hides the popup and detaches it from the page. This has no effect if it
* is not currently showing.
*
* @param autoClosed
* the value that will be passed to
* {@link CloseHandler#onClose(CloseEvent)} when the popup is
* closed
*/
protected void hide(final boolean autoClosed)
{
doHide(true, autoClosed, isAnimationEnabled());
}
@Override
protected void onUnload()
{
super.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, true, false, null);
}
}
/**
* Fires a blur event to the element.
* @param elt the element.
*/
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();
}
}-*/;
private void centralizeMe()
{
popupCentralizer.centralize(this);
left = -1;
top = -1;
}
private void doHide(boolean fireEvent, final boolean autoClosed, boolean animated)
{
if (!isShowing())
{
return;
}
if (animated && popupCentralizer.isCentralized())
{
fixPositionToCenter();
}
if (fireEvent)
{
setState(false, false, animated, new StateChangeCallback()
{
@Override
public void onStateChange()
{
CloseEvent.fire(PopupPanel.this, PopupPanel.this, autoClosed);
}
});
}
else
{
setState(false, false, animated, null);
}
}
private void doShow(final boolean animated)
{
if (isShowing())
{
return;
}
else if (isAttached())
{
// The popup is attached directly to another panel, so we need to
// remove
// it from its parent before showing it. This is a weird use case,
// but
// since PopupPanel is a Widget, its legal.
this.removeFromParent();
}
if (popupCentralizer.isCentralized() && animated)
{
setVisible(false);
setState(true, false, false, new StateChangeCallback()
{
@Override
public void onStateChange()
{
OpenEvent.fire(PopupPanel.this, PopupPanel.this);
}
});
fixPositionToCenter();
setVisible(true);
runEntranceAnimation(new StateChangeCallback()
{
@Override
public void onStateChange()
{
centralizeMe();
}
});
}
else
{
setState(true, false, animated, new StateChangeCallback()
{
@Override
public void onStateChange()
{
OpenEvent.fire(PopupPanel.this, PopupPanel.this);
}
});
}
}
/**
* 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 (int i=0; i < autoHidePartners.size(); i++)
{
Element elem = autoHidePartners.get(i);
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))
{
return getElement().isOrHasChild(Element.as(target));
}
return false;
}
private void fixPositionToCenter()
{
int left = getPopupLeftToCenter(false);
int top = getPopupTopToCenter(false);
setPosition(left, top);
}
private InOutAnimation getDialogAnimation()
{
if (animation == null)
{
animation = InOutAnimation.bounce;
}
return animation;
}
private int getLeftRelativeObject(final UIObject relativeObject)
{
int offsetWidth = getOffsetWidth();
int relativeElemOffsetWidth = relativeObject.getOffsetWidth();
int offsetWidthDiff = offsetWidth - relativeElemOffsetWidth;
int left;
if (LocaleInfo.getCurrentLocale().isRTL())
{ // RTL case
int relativeElemAbsoluteLeft = relativeObject.getAbsoluteLeft();
left = relativeElemAbsoluteLeft - offsetWidthDiff;
if (offsetWidthDiff > 0)
{
int windowRight = Window.getClientWidth() + Window.getScrollLeft();
int windowLeft = Window.getScrollLeft();
int relativeElemLeftValForRightEdge = relativeElemAbsoluteLeft + relativeElemOffsetWidth;
int distanceToWindowRight = windowRight - relativeElemLeftValForRightEdge;
int distanceFromWindowLeft = relativeElemLeftValForRightEdge - windowLeft;
if (distanceFromWindowLeft < offsetWidth && distanceToWindowRight >= offsetWidthDiff)
{
left = relativeElemAbsoluteLeft;
}
}
}
else
{ // LTR case
left = relativeObject.getAbsoluteLeft();
if (offsetWidthDiff > 0)
{
int windowRight = Window.getClientWidth() + Window.getScrollLeft();
int windowLeft = Window.getScrollLeft();
int distanceToWindowRight = windowRight - left;
int distanceFromWindowLeft = left - windowLeft;
if (distanceToWindowRight < offsetWidth && distanceFromWindowLeft >= offsetWidthDiff)
{
left -= offsetWidthDiff;
}
}
}
return left;
}
/**
* Gets the popup's left position relative to the browser's center area.
* @param includeScroll if true include the window scroll
* @return the popup's left position
*/
private int getPopupLeftToCenter(boolean includeScroll)
{
int windowLeft = includeScroll?Window.getScrollLeft():0;
int windowWidth = Window.getClientWidth();
int centerLeft = (windowWidth / 2) + windowLeft;
int offsetWidth = getOffsetWidth();
return centerLeft - (offsetWidth / 2);
}
/**
* Gets the popup's top position relative to the browser's center area.
* @param includeScroll if true include the window scroll
* @return the popup's top position
*/
private int getPopupTopToCenter(boolean includeScroll)
{
int windowTop = includeScroll?Window.getScrollTop():0;
int windowHeight = Window.getClientHeight();
int centerTop = (windowHeight / 2) + windowTop;
int offsetHeight = getOffsetHeight();
return centerTop - (offsetHeight / 2);
}
private int getTopRelativeObject(final UIObject relativeObject)
{
int offsetHeight = getOffsetHeight();
int top = relativeObject.getAbsoluteTop();
int windowTop = Window.getScrollTop();
int windowBottom = Window.getScrollTop() + Window.getClientHeight();
int distanceFromWindowTop = top - windowTop;
int distanceToWindowBottom = windowBottom - (top + relativeObject.getOffsetHeight());
if (distanceToWindowBottom < offsetHeight && distanceFromWindowTop >= offsetHeight)
{
top -= offsetHeight;
}
else
{
top += relativeObject.getOffsetHeight();
}
return top;
}
/**
* Show or hide the glass.
*/
private void maybeShowGlass()
{
BodyElement body = Document.get().getBody();
if (isShowing())
{
if (modal)
{
try
{
body.appendChild(glass);
if(numberCalls == 0)
{
body.addClassName(FacesBackboneResourcesCommon.INSTANCE.css().facesUnscrollable());
body.addClassName(FacesBackboneResourcesCommon.INSTANCE.css().facesUnselectable());
}
glassShowing = true;
}
finally
{
numberCalls++;
}
}
}
else if (glassShowing)
{
try
{
body.removeChild(glass);
if(numberCalls == 1)
{
body.removeClassName(FacesBackboneResourcesCommon.INSTANCE.css().facesUnscrollable());
body.removeClassName(FacesBackboneResourcesCommon.INSTANCE.css().facesUnselectable());
}
glassShowing = false;
}
finally
{
numberCalls--;
}
}
}
private void removePopupFromDOM()
{
if (popupCentralizer.isCentralized())
{
fixPositionToCenter();
}
RootPanel.get().remove(PopupPanel.this);
setPopupPositionStyle(left, top);
}
private void runEntranceAnimation(final StateChangeCallback callback)
{
animating = true;
getDialogAnimation().animateEntrance(this, new Animation.Callback()
{
@Override
public void onAnimationCompleted()
{
animating = false;
if (callback != null)
{
callback.onStateChange();
}
}
}, animationDuration);
}
private void setPopupPositionStyle(int left, int top)
{
Style style = getElement().getStyle();
style.setPropertyPx("left", left);
style.setPropertyPx("top", top);
}
private void setState(boolean showing, boolean unloading, boolean animated, final StateChangeCallback callback)
{
this.showing = showing;
updateHandlers();
maybeShowGlass();
if (isShowing())
{
if (animated)
{
animating = true;
getDialogAnimation().animateEntrance(this, new Animation.Callback()
{
@Override
public void onAnimationCompleted()
{
animating = false;
if (callback != null)
{
callback.onStateChange();
}
}
}, animationDuration);
}
RootPanel.get().add(this);
if (!animated && callback != null)
{
callback.onStateChange();
}
}
else
{
if (!unloading)
{
if (animated)
{
animating = true;
getDialogAnimation().animateExit(this, new Animation.Callback(){
@Override
public void onAnimationCompleted()
{
animating = false;
removePopupFromDOM();
if (callback != null)
{
callback.onStateChange();
}
}
}, animationDuration);
}
else
{
removePopupFromDOM();
if (callback != null)
{
callback.onStateChange();
}
}
}
}
}
private void uncentralizeMe()
{
popupCentralizer.descentralize(this);
}
/**
* Register or unregister the handlers used by {@link PopupPanel}.
*/
private void updateHandlers()
{
// Remove any existing handlers.
if (nativePreviewHandlerRegistration != null)
{
nativePreviewHandlerRegistration.removeHandler();
nativePreviewHandlerRegistration = null;
}
if (historyHandlerRegistration != null)
{
historyHandlerRegistration.removeHandler();
historyHandlerRegistration = null;
}
// Create handlers if showing.
if (isShowing())
{
nativePreviewHandlerRegistration = Event.addNativePreviewHandler(this);
historyHandlerRegistration = History.addValueChangeHandler(new ValueChangeHandler<String>()
{
public void onValueChange(ValueChangeEvent<String> event)
{
if (autoHideOnHistoryEvents)
{
hide();
}
}
});
}
}
/**
* Add a default close handler that will be appended to each created object
* @param defaultCloseHandler
*/
public static void addDefaultCloseHandler(CloseHandler<PopupPanel> defaultCloseHandler)
{
if(defaultCloseHandlers == null)
{
defaultCloseHandlers = new ArrayList<CloseHandler<PopupPanel>>();
}
defaultCloseHandlers.add(defaultCloseHandler);
}
/**
* Add a default open handler that will be appended to each created object
* @param defaultOpenHandler
*/
public static void addDefaultOpenHandler(OpenHandler<PopupPanel> defaultOpenHandler)
{
if(defaultOpenHandlers == null)
{
defaultOpenHandlers = new ArrayList<OpenHandler<PopupPanel>>();
}
defaultOpenHandlers.add(defaultOpenHandler);
}
public static abstract class PopupCentralizer
{
boolean centralized = false;
/**
* Centralize the popup.
* @param uiObject
*/
public abstract void centralize(UIObject uiObject);
/**
* Descentralize the popup.
* @param uiObject
*/
public abstract void descentralize(UIObject uiObject);
/**
* @return true if the popup is centralized and
* false otherwise.
*/
public boolean isCentralized()
{
return centralized;
}
/**
* @param centralized
*/
protected void setCentralized(boolean centralized)
{
this.centralized = centralized;
}
}
private static interface StateChangeCallback
{
void onStateChange();
}
}