/*
* Copyright 2007 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.
*
* Contributers:
* Darell Meyer <darrell@mygwt.net> - Derived implementation
*/
package net.mygwt.ui.client.widget;
import net.mygwt.ui.client.Events;
import net.mygwt.ui.client.MyDOM;
import net.mygwt.ui.client.event.BaseEvent;
import net.mygwt.ui.client.event.Listener;
import net.mygwt.ui.client.fx.FXStyle;
import net.mygwt.ui.client.util.Point;
import net.mygwt.ui.client.util.Rectangle;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventPreview;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetHelper;
import com.google.gwt.user.client.ui.impl.PopupImpl;
/**
* A shadow panel that can "pop up" over other widgets. It overlays the
* browser's client area (and any previously-created popups).
*
* <dl>
* <dt><b>Events:</b></dt>
*
* <dd><b>BeforeShow</b> : (widget)<br>
* <div>Fires before the popup is displayed. Listeners can set the
* <code>doit</code> field to <code>false</code> to cancel the action.</div>
* <ul>
* <li>widget : this</li>
* </ul>
* </dd>
*
* <dd><b>Show</b> : (widget)<br>
* <div>Fires after a shell is shown.</div>
* <ul>
* <li>widget : this</li>
* </ul>
* </dd>
*
* <dd><b>BeforeHide</b> : (widget)<br>
* <div>Fires before the popup is hidden. Listeners can set the
* <code>doit</code> field to <code>false</code> to cancel the action.</div>
* <ul>
* <li>widget : this</li>
* </ul>
* </dd>
*
* <dd><b>Hide</b> : (widget)<br>
* <div>Fires after a shell is hidden.</div>
* <ul>
* <li>widget : this</li>
* </ul>
* </dd>
*
* <dt><b>CSS:</b></dt>
* <dd>.my-popup (the popup itself)</dd>
* </dl>
*/
public class Popup extends Component implements EventPreview {
private static PopupImpl impl = (PopupImpl) GWT.create(PopupImpl.class);
/**
* animate specifies if the hiding and showing of the panel should be
* animated. Default value is <code>true</code>.
*/
public boolean animate = true;
/**
* eventPreview specifies if event preview should be activated when the popup
* is displayed. Default value is <code>true</code>
*/
public boolean eventPreview = true;
/**
* autoFocus specifies if focus should be set on the popup when displayed.
* Default value is <code>true</code>
*/
public boolean autoFocus = true;
/**
* Specifies the vertical offset. Only applies when constrainViewerport is
* <code>true</code>. Default value is 15.
*/
public int topOffset = 15;
/**
* leftOffset specifies the horizontal offset. Only applies when
* constrainViewerport is <code>true</code>. Default value is 10.
*/
public int leftOffset = 10;
/**
* constrainViewport specifies if the popup location should be forced to the
* viewport area. Default value is <code>true</code>
*/
public boolean constrainViewport = true;
/**
* duration specifies the length of the fade effect in milliseconds. Default
* value is 200.
*/
public int duration = 200;
private boolean showing, autoHide;
private Widget widget;
/**
* Creates a new popup panel.
*/
public Popup() {
}
/**
* Creates an new popup panel.
*
* @param autoHide <code>true</code> to hide if "click" occurs outside of
* the popup
*/
public Popup(boolean autoHide) {
this();
this.autoHide = autoHide;
}
/**
* Centers the panel within the viewport.
*/
public void center() {
MyDOM.center(getElement());
}
/**
* Hides the popup. This has no effect if it is not currently visible.
*/
public void hide() {
if (!showing) return;
showing = false;
if (eventPreview) {
DOM.removeEventPreview(this);
}
if (animate) {
FXStyle fx = new FXStyle(getElement());
fx.duration = duration;
fx.addListener(Events.EffectComplete, new Listener() {
public void handleEvent(BaseEvent be) {
afterHide();
}
});
fx.fadeOut();
} else {
afterHide();
}
}
public boolean isVisible() {
return showing;
}
public boolean onEventPreview(Event event) {
int type = DOM.eventGetType(event);
Element target = DOM.eventGetTarget(event);
switch (type) {
case Event.ONMOUSEDOWN:
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) {
// Disallow mouse events outside of the popup.
if (!DOM.isOrHasChild(getElement(), target)) {
// If it's a click event, and auto-hide is enabled: hide the popup
// and _don't_ eat the event.
if (autoHide && (type == Event.ONCLICK)) {
if (onAutoHide(event)) {
hide();
return true;
}
return false;
}
return false;
}
}
break;
}
case Event.ONKEYUP:
int code = DOM.eventGetKeyCode(event);
switch (code) {
case KeyboardListener.KEY_ESCAPE:
hide();
}
break;
}
return true;
}
/**
* Sets the popup's content.
*
* @param widget the content widget
*/
public void setWidget(Widget widget) {
this.widget = widget;
}
/**
* Displays the popup. It must have a child widget before this method is
* called.
*/
public void show() {
if (showing) {
return;
}
showing = true;
DOM.appendChild(getElement(), widget.getElement());
if (constrainViewport) {
int clientHeight = Window.getClientHeight() + MyDOM.getBodyScrollTop();
int clientWidth = Window.getClientWidth() + MyDOM.getBodyScrollLeft();
Rectangle r = MyDOM.getBounds(getElement());
int x = r.x;
int y = r.y;
if (y + r.height > clientHeight) {
y = y - topOffset - r.height;
MyDOM.setTop(getElement(), y);
}
if (x + r.width > clientWidth) {
x = x - leftOffset - r.width;
MyDOM.setLeft(getElement(), x);
}
}
MyDOM.setVisibility(getElement(), false);
setStyleAttribute("position", "absolute");
RootPanel.get().add(this);
impl.onShow(getElement());
WidgetHelper.doAttach(widget);
MyDOM.setVisibility(getElement(), true);
if (eventPreview) {
DOM.addEventPreview(this);
}
if (animate) {
FXStyle fx = new FXStyle(getElement());
fx.duration = duration;
fx.addListener(Events.EffectComplete, new Listener() {
public void handleEvent(BaseEvent be) {
afterShow();
}
});
fx.fadeIn();
} else {
afterShow();
}
}
/**
* Shows the popup at the specified location.
*
* @param x the x coordinate
* @param y the y coordinate
*/
public void show(int x, int y) {
DOM.appendChild(getElement(), widget.getElement());
MyDOM.setLeftTop(getElement(), x, y);
show();
}
/**
* @param elem
* @param pos
*/
public void show(Element elem, String pos) {
DOM.appendChild(getElement(), widget.getElement());
Point p = MyDOM.getAlignToXY(getElement(), elem, pos, null);
MyDOM.setLeftTop(getElement(), p.x, p.y);
show();
}
public void show(Element elem, String pos, int[] offsets) {
DOM.appendChild(getElement(), widget.getElement());
Point p = MyDOM.getAlignToXY(getElement(), elem, pos, offsets);
MyDOM.setLeftTop(getElement(), p.x, p.y);
show();
}
/**
* Displays the popup aligned to the bottom left of the widget. For exact
* control of popup position see {@link MyDOM#alignTo}.
*
* @param widget the widget to use for alignment
*/
public void show(Widget widget) {
int[] offset = new int[] {0, 2};
Point p = MyDOM.getAlignToXY(getElement(), widget.getElement(), null, offset);
MyDOM.setLeftTop(getElement(), p.x, p.y);
show();
}
protected void afterHide() {
impl.onHide(getElement());
RootPanel.get().remove(this);
showing = false;
WidgetHelper.doDetach(widget);
fireEvent(Events.Hide);
}
protected void afterShow() {
if (autoFocus) {
MyDOM.setFocus(getElement(), true);
}
fireEvent(Events.Show);
}
protected boolean onAutoHide(Event event) {
return true;
}
protected void onRender() {
setElement(DOM.createDiv());
setStyleName("my-popup");
setStyleAttribute("position", "absolute");
setStyleAttribute("zIndex", "100");
}
}