/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.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 com.google.gwt.user.client.ui;
import java.util.List;
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.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.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
public class ModalLayoutPanel extends LayoutPanel implements
HasCloseHandlers<ModalLayoutPanel> {
private boolean showing;
private List<Element> autoHidePartners;
private HandlerRegistration nativePreviewHandlerRegistration;
private HandlerRegistration resizeHandlerRegistration;
public HandlerRegistration addCloseHandler(
CloseHandler<ModalLayoutPanel> handler) {
return addHandler(handler, CloseEvent.getType());
}
/**
* 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 autoClosed 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);
CloseEvent.fire(this, this, autoClosed);
}
/**
* 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"));
}
/**
* 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);
}
}
/**
* 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.
DOM.setStyleAttribute(getElement(), "visibility", visible ? "visible"
: "hidden");
}
/**
* 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);
}
protected void onPreviewNativeEvent(NativePreviewEvent event) {
}
@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);
}
}
/**
* 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)) {
return getElement().isOrHasChild(Element.as(target));
}
return false;
}
/**
* 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() || 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
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.
event.cancel();
}
/**
* Set the showing state of the popup.
*
* @param showing the new state
*/
private void setState(boolean showing) {
this.showing = showing;
// Create or remove the native preview handler
if (showing) {
RootPanel.get().add(this);
getLayout().onAttach();
getLayout().fillParent();
nativePreviewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
public void onPreviewNativeEvent(NativePreviewEvent event) {
previewNativeEvent(event);
}
});
resizeHandlerRegistration = Window.addResizeHandler(new ResizeHandler() {
public void onResize(ResizeEvent event) {
ModalLayoutPanel.this.onResize();
}
});
} else if (nativePreviewHandlerRegistration != null) {
nativePreviewHandlerRegistration.removeHandler();
nativePreviewHandlerRegistration = null;
resizeHandlerRegistration.removeHandler();
resizeHandlerRegistration = null;
RootPanel.get().remove(this);
}
}
}