package com.google.gwt.user.client.ui;
/*
* 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.
*/
import net.thesocialos.client.TheSocialOS;
import net.thesocialos.client.desktop.window.events.WindowDisplay;
import net.thesocialos.client.desktop.window.events.WindowEndDragEvent;
import net.thesocialos.client.desktop.window.events.WindowEvent;
import net.thesocialos.client.desktop.window.events.WindowEventHandler;
import net.thesocialos.client.desktop.window.events.WindowOnTopEvent;
import net.thesocialos.client.desktop.window.events.WindowResizeEvent;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.HasAllMouseHandlers;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
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.safehtml.client.HasSafeHtml;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
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.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Window;
import com.google.web.bindery.event.shared.SimpleEventBus;
/**
* A form of popup that has a caption area at the top and can be dragged by the user. Unlike a PopupPanel, calls to
* {@link #setWidth(String)} and {@link #setHeight(String)} will set the width and height of the dialog box itself, even
* if a widget has not been added as yet.
* <p>
* <img class='gallery' src='doc-files/DialogBox.png'/>
* </p>
* <h3>CSS Style Rules</h3>
*
* <ul>
* <li>.gwt-DialogBox { the outside of the dialog }</li>
* <li>.gwt-DialogBox .Caption { the caption }</li>
* <li>.gwt-DialogBox .dialogContent { the wrapper around the content }</li>
* <li>.gwt-DialogBox .dialogTopLeft { the top left cell }</li>
* <li>.gwt-DialogBox .dialogTopLeftInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogTopCenter { the top center cell, where the caption is located }</li>
* <li>.gwt-DialogBox .dialogTopCenterInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogTopRight { the top right cell }</li>
* <li>.gwt-DialogBox .dialogTopRightInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogMiddleLeft { the middle left cell }</li>
* <li>.gwt-DialogBox .dialogMiddleLeftInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogMiddleCenter { the middle center cell, where the content is located }</li>
* <li>.gwt-DialogBox .dialogMiddleCenterInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogMiddleRight { the middle right cell }</li>
* <li>.gwt-DialogBox .dialogMiddleRightInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogBottomLeft { the bottom left cell }</li>
* <li>.gwt-DialogBox .dialogBottomLeftInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogBottomCenter { the bottom center cell }</li>
* <li>.gwt-DialogBox .dialogBottomCenterInner { the inner element of the cell }</li>
* <li>.gwt-DialogBox .dialogBottomRight { the bottom right cell }</li>
* <li>.gwt-DialogBox .dialogBottomRightInner { the inner element of the cell }</li>
* </ul>
* <p>
* <h3>Example</h3>
* {@example com.google.gwt.examples.DialogBoxExample}
* </p>
*
* <h3>Use in UiBinder Templates</h3>
* <p>
* DialogBox elements in {@link com.google.gwt.uibinder.client.UiBinder UiBinder} templates can have one widget child
* and one <g:caption> child. (Note the lower case "c", meant to signal that the caption is not a runtime object, and
* so cannot have a <code>ui:field</code> attribute.) The body of the caption can be html.
*
* <p>
*
* For example:
*
* <pre>
* <g:DialogBox autoHide="true" modal="true">
* <g:caption><b>Caption text</b></g:caption>
* <g:HTMLPanel>
* Body text
* <g:Button ui:field='cancelButton'>Cancel</g:Button>
* <g:Button ui:field='okButton'>Okay</g:Button>
* </g:HTMLPanel>
* </g:DialogBox>
* </pre>
*
* You may also create your own header caption. The caption must implement {@link Caption}.
*
* <p>
*
* For example:
*
* <p>
*
* <pre>
* <g:DialogBox autoHide="true" modal="true">
* <-- foo is your prefix and Bar is a class that implements {@link Caption}-->
* <g:customCaption><foo:Bar/></g:customCaption>
* <g:HTMLPanel>
* Body text
* <g:Button ui:field='cancelButton'>Cancel</g:Button>
* <g:Button ui:field='okButton'>Okay</g:Button>
* </g:HTMLPanel>
* </g:DialogBox>
* </pre>
*
*/
public class WindowPanelLayout extends DecoratedPopupPanel implements HasHTML, HasSafeHtml, WindowDisplay {
/**
* Set of characteristic interfaces supported by the {@link DialogBox} caption.
*
*/
public interface Caption extends HasAllMouseHandlers, HasHTML, HasSafeHtml, IsWidget {
public void addWindowEventBus(SimpleEventBus windowEventBus);
int getHeight();
}
/**
* Default implementation of Caption. This will be created as the header if there isn't a header specified.
*/
public static class CaptionImpl extends HTML implements Caption {
public CaptionImpl() {
super();
setStyleName("Caption");
}
@Override
public void addWindowEventBus(SimpleEventBus windowEventBus) {
// TODO Auto-generated method stub
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return 0;
}
}
/**
* Set of characteristic interfaces supported by the {@link DialogBox} caption.
*
*/
public interface Footer extends HasAllMouseHandlers, HasHTML, HasSafeHtml, IsWidget {
int getHeight();
}
/**
* Default implementation of Caption. This will be created as the header if there isn't a header specified.
*/
public static class FooterImpl extends HTML implements Footer {
public FooterImpl() {
super();
setStyleName("Footer");
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return 0;
}
}
/**
* The default style name.
*/
private static final String DEFAULT_STYLENAME = "sos-Window";
private boolean isMaximized = false;
private boolean isResizable = true;
private Caption caption;
private Footer footer;
private boolean dragging;
private boolean resizing;
private int dragStartX, dragStartY;
private int windowWidth;
private int clientLeft;
private int clientTop;
SimplePanel panel = new SimplePanel();
private HandlerRegistration resizeHandlerRegistration;
private HandlerRegistration NativeEventHandlerRegistration;
private SimpleEventBus windowEventBus = new SimpleEventBus();
/**
* Creates an empty dialog box. It should not be shown until its child widget has been added using
* {@link #add(Widget)}.
*/
public WindowPanelLayout() {
this(false);
}
/**
* Creates an empty dialog box specifying its "auto-hide" property. It should not be shown until its child widget
* has been added using {@link #add(Widget)}.
*
* @param autoHide
* <code>true</code> if the dialog should be automatically hidden when the user clicks outside of it
*/
public WindowPanelLayout(boolean autoHide) {
this(autoHide, true);
}
/**
* Creates an empty dialog box specifying its "auto-hide" and "modal" properties. It should not be shown until its
* child widget has been added using {@link #add(Widget)}.
*
* @param autoHide
* <code>true</code> if the dialog should be automatically hidden when the user clicks outside of it
* @param modal
* <code>true</code> if keyboard and mouse events for widgets not contained by the dialog should be
* ignored
*/
public WindowPanelLayout(boolean autoHide, boolean modal) {
this(autoHide, modal, new CaptionImpl(), new FooterImpl());
}
/**
*
* Creates an empty dialog box specifying its "auto-hide", "modal" properties and an implementation a custom
* {@link Caption}. It should not be shown until its child widget has been added using {@link #add(Widget)}.
*
* @param autoHide
* <code>true</code> if the dialog should be automatically hidden when the user clicks outside of it
* @param modal
* <code>true</code> if keyboard and mouse events for widgets not contained by the dialog should be
* ignored
* @param captionWidget
* the widget that is the DialogBox's header.
*/
public WindowPanelLayout(boolean autoHide, boolean modal, Caption captionWidget, Footer footerWidget) {
super(autoHide, modal, "dialog");
assert captionWidget != null : "The caption must not be null";
assert footerWidget != null : "The footer must not be null";
captionWidget.asWidget().removeFromParent();
footerWidget.asWidget().removeFromParent();
caption = captionWidget;
footer = footerWidget;
panel.setStyleName("invisiblePanel");
// Add the caption to the top row of the decorator panel. We need to
// logically adopt the caption so we can catch mouse events.
Element td = getCellElement(0, 1);
DOM.appendChild(td, caption.asWidget().getElement());
Element tf = getCellElement(2, 1);
DOM.appendChild(tf, footer.asWidget().getElement());
// adopt(footer.asWidget());
// adopt(caption.asWidget());
// Set the style name
setStyleName(DEFAULT_STYLENAME);
windowWidth = Window.getClientWidth();
clientLeft = Document.get().getBodyOffsetLeft();
clientTop = Document.get().getBodyOffsetTop();
caption.addWindowEventBus(windowEventBus);
caption.addMouseDownHandler(new MouseDownHandler() {
@Override
public void onMouseDown(MouseDownEvent event) {
if (!isMaximized) {
windowEventBus.fireEvent(new WindowOnTopEvent());
beginDragging(event);
}
}
});
footer.addMouseDownHandler(new MouseDownHandler() {
@Override
public void onMouseDown(MouseDownEvent event) {
if (!isMaximized && isResizable) {
windowEventBus.fireEvent(new WindowOnTopEvent());
beginResizing(event);
}
}
});
// addDomHandler(mouseHandler, MouseMoveEvent.getType());
}
/**
* Creates an empty dialog box specifying its {@link Caption}. It should not be shown until its child widget has
* been added using {@link #add(Widget)}.
*
* @param captionWidget
* the widget that is the DialogBox's header.
*/
public WindowPanelLayout(Caption captionWidget, Footer footerWidget) {
this(false, true, captionWidget, footerWidget);
}
@Override
public com.google.web.bindery.event.shared.HandlerRegistration addWindowEvents(WindowEventHandler handler) {
// TODO Auto-generated method stub
return windowEventBus.addHandler(WindowEvent.TYPE, handler);
}
protected void beginDragging(MouseDownEvent event) {
TheSocialOS.get().getDesktop().add(panel);
if (DOM.getCaptureElement() == null) {
/*
* Need to check to make sure that we aren't already capturing an element otherwise events will not fire as
* expected. If this check isn't here, any class which extends custom button will not fire its click event
* for example.
*/
dragging = true;
DOM.setCapture(getElement());
dragStartX = event.getX();
dragStartY = event.getY();
moveNativeHandler();
}
}
protected void beginResizing(MouseDownEvent event) {
TheSocialOS.get().getDesktop().add(panel);
if (DOM.getCaptureElement() == null) {
/*
* Need to check to make sure that we aren't already capturing an element otherwise events will not fire as
* expected. If this check isn't here, any class which extends custom button will not fire its click event
* for example.
*/
resizing = true;
DOM.setCapture(getElement());
dragStartX = event.getClientX();
dragStartY = event.getClientY();
moveNativeHandler();
}
}
/**
* Check the window position into the navigation window If the window excess the limits, this method will resize the
* windows into a limits
*/
private void checkSpace() {
if (getAbsoluteLeft() < 0) setPopupPosition(0, getAbsoluteTop());
else if (getAbsoluteLeft() + getOffsetWidth() > Window.getClientWidth())
setPopupPosition(Window.getClientWidth() - getOffsetWidth(), getAbsoluteTop());
if (getAbsoluteTop() + getOffsetHeight() > Window.getClientHeight()) setPopupPosition(getAbsoluteLeft(),
Window.getClientHeight() - 30 - getOffsetHeight());
else if (getAbsoluteTop() < 30) setPopupPosition(getAbsoluteLeft(), 0);
}
/**
* Called on mouse move in the caption area, continues dragging if it was started by {@link #beginDragging}.
*
* @see #beginDragging
* @see #endDragging
* @param event
* the mouse move event that continues dragging
*/
protected void continueDragging(int x, int y) {
// onMouseMove(caption.asWidget(), event.getX(), event.getY());
if (dragging) {
int absX = x; // + getAbsoluteLeft();
System.out.println(x + " x " + y + " y");
int absY = y;// + getAbsoluteTop();
// if the mouse is off the screen to the left, right, or top, don't
// move the dialog box. This would let users lose dialog boxes, which
// would be bad for modal popups.
if (absX < clientLeft || absX >= windowWidth || absY < clientTop) return;
setPopupPosition(absX - dragStartX, absY - dragStartY - 30);
checkSpace();
}
}
protected void continueResizing(int x, int y) {
if (resizing) {
// System.out.println(x + " " + y);
int absX = x; // + getAbsoluteLeft();
int absY = y;// + getAbsoluteTop();
// if the mouse is off the screen to the left, right, or top, don't
// move the dialog box. This would let users lose dialog boxes, which
// would be bad for modal popups.
if (x >= Window.getClientWidth()) {
setWidth(Window.getClientWidth() - getAbsoluteLeft() + "px");
getWidget().setWidth(Window.getClientWidth() - getAbsoluteLeft() - 7 + "px");
return;
}
if (y >= Window.getClientHeight()) {
setHeight(Window.getClientHeight() - getAbsoluteTop() + "px");
getWidget().setHeight(
Window.getClientHeight() - getAbsoluteTop() - caption.getHeight() - footer.getHeight() - 7
+ "px");
return;
}
// if (absX < clientLeft || absX >= windowWidth || absY < clientTop) {
// return;
// }
if ((x - getAbsoluteLeft()) < 0) return;
if ((y - getAbsoluteTop() - caption.getHeight() - footer.getHeight()) < 0) return;
setWidth((x - getAbsoluteLeft()) + "px");
getWidget().setWidth((x - getAbsoluteLeft()) + "px");
setHeight((y - getAbsoluteTop()) + "px");
getWidget().setHeight((y - getAbsoluteTop() - caption.getHeight() - footer.getHeight()) + "px");
windowEventBus.fireEvent(new WindowResizeEvent());
// checkResizeSpace();
}
}
/**
* Enables or disables text selection for the element. A circular reference will be created when disabling text
* selection. Disabling should e cleared when the element is detached. See the <code>Component</code> source for an
* example.
*
* @param elem
* the element
* @param disable
* <code>true</code> to disable
*/
@Override
protected void doAttachChildren() {
try {
super.doAttachChildren();
} finally {
// See comment in doDetachChildren for an explanation of this call
caption.asWidget().onAttach();
footer.asWidget().onAttach();
}
}
@Override
protected void doDetachChildren() {
try {
super.doDetachChildren();
} finally {
/*
* We need to detach the caption specifically because it is not part of the iterator of Widgets that the
* {@link SimplePanel} super class returns. This is similar to a {@link ComplexPanel}, but we do not want to
* expose the caption widget, as its just an internal implementation.
*/
caption.asWidget().onDetach();
footer.asWidget().onDetach();
}
}
/**
* Called on mouse up in the caption area, ends dragging by ending event capture.
*
* @param event
* the mouse up event that ended dragging
*
* @see DOM#releaseCapture
* @see #beginDragging
* @see #endDragging
*/
protected void endDragging() {
TheSocialOS.get().getDesktop().remove(panel);
windowEventBus.fireEvent(new WindowEndDragEvent());
dragging = false;
NativeEventHandlerRegistration.removeHandler();
DOM.releaseCapture(getElement());
}
protected void endResizing() {
TheSocialOS.get().getDesktop().remove(panel);
// windowEventBus.fireEvent(new WindowEndDragEvent());
resizing = false;
NativeEventHandlerRegistration.removeHandler();
DOM.releaseCapture(getElement());
}
/**
* Provides access to the dialog's caption.
*
* @return the logical caption for this dialog box
*/
public Caption getCaption() {
return caption;
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return getOffsetHeight();
}
@Override
public String getHTML() {
return caption.getHTML();
}
@Override
public String getText() {
return caption.getText();
}
@Override
public int getWidth() {
// TODO Auto-generated method stub
return getOffsetWidth();
}
@Override
public WindowPanelLayout getWindow() {
// TODO Auto-generated method stub
return this;
}
@Override
public int getXposition() {
// TODO Auto-generated method stub
return getAbsoluteLeft();
}
@Override
public int getYPosition() {
// TODO Auto-generated method stub
return getAbsoluteTop();
}
@Override
public void hide() {
if (resizeHandlerRegistration != null) {
resizeHandlerRegistration.removeHandler();
resizeHandlerRegistration = null;
}
super.hide();
}
private boolean isCaptionEvent(NativeEvent event) {
EventTarget target = event.getEventTarget();
if (com.google.gwt.dom.client.Element.is(target))
return getCellElement(0, 1).getParentElement().isOrHasChild(com.google.gwt.dom.client.Element.as(target));
return false;
}
/**
* Called on mouse down in the caption area, begins the dragging loop by turning on event capture.
*
* @see DOM#setCapture
* @see #continueDragging
* @param event
* the mouse down event that triggered dragging
*/
private void moveNativeHandler() {
NativeEventHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
@Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
// TODO Auto-generated method stub
if (Event.ONMOUSEMOVE == event.getTypeInt()) {
if (dragging) continueDragging(event.getNativeEvent().getClientX(), event.getNativeEvent()
.getClientY());
else
continueResizing(event.getNativeEvent().getClientX(), event.getNativeEvent().getClientY());
} else if (Event.ONMOUSEUP == event.getTypeInt()) if (dragging) endDragging();
else
endResizing();
event.cancel();
}
});
}
@Override
public void onBrowserEvent(Event event) {
// If we're not yet dragging, only trigger mouse events if the event occurs
// in the caption wrapper
switch (event.getTypeInt()) {
case Event.ONMOUSEDOWN:
case Event.ONMOUSEUP:
case Event.ONMOUSEMOVE:
case Event.ONMOUSEOVER:
case Event.ONMOUSEOUT:
if (!dragging && !isCaptionEvent(event)) return;
}
super.onBrowserEvent(event);
}
/**
* <b>Affected Elements:</b>
* <ul>
* <li>-caption = text at the top of the {@link DialogBox}.</li>
* <li>-content = the container around the content.</li>
* </ul>
*
* @see UIObject#onEnsureDebugId(String)
*/
@Override
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
caption.asWidget().ensureDebugId(baseID + "-caption");
ensureDebugId(getCellElement(1, 1), baseID, "content");
}
@Override
protected void onPreviewNativeEvent(NativePreviewEvent event) {
// We need to preventDefault() on mouseDown events (outside of the
// DialogBox content) to keep text from being selected when it
// is dragged.
NativeEvent nativeEvent = event.getNativeEvent();
if (!event.isCanceled() && (event.getTypeInt() == Event.ONMOUSEDOWN) && isCaptionEvent(nativeEvent))
nativeEvent.preventDefault();
super.onPreviewNativeEvent(event);
}
/**
* Sets the html string inside the caption by calling its {@link #setHTML(SafeHtml)} method.
*
* Use {@link #setWidget(Widget)} to set the contents inside the {@link DialogBox}.
*
* @param html
* the object's new HTML
*/
@Override
public void setHTML(SafeHtml html) {
caption.setHTML(html);
}
/**
* Sets the html string inside the caption by calling its {@link #setHTML(SafeHtml)} method. Only known safe HTML
* should be inserted in here.
*
* Use {@link #setWidget(Widget)} to set the contents inside the {@link DialogBox}.
*
* @param html
* the object's new HTML
*/
@Override
public void setHTML(String html) {
caption.setHTML(SafeHtmlUtils.fromTrustedString(html));
}
@Override
public void setMaximized(Boolean maximized) {
isMaximized = maximized;
}
@Override
public void setMinimized(Boolean minimized) {
setVisible(!minimized);
}
@Override
public void setPosition(int x, int y) {
setPopupPosition(x, y);
}
@Override
public void setSize(int width, int height) {
// setWidth(width - 25 + "px");
getWidget().setWidth(width - 27 + "px");
// setHeight(height - 25 + "px");
getWidget().setHeight(height - caption.getHeight() - footer.getHeight() - 9 + "px");
// checkSpace();
}
/**
* Sets the text inside the caption by calling its {@link #setText(String)} method.
*
* Use {@link #setWidget(Widget)} to set the contents inside the {@link DialogBox}.
*
* @param text
* the object's new text
*/
@Override
public void setText(String text) {
caption.setText(text);
}
@Override
public void setWindowTitle(String text) {
caption.setText(text);
}
@Override
public String getWindowTitle() {
// TODO Auto-generated method stub
return caption.getText();
}
@Override
public void show() {
if (resizeHandlerRegistration == null) resizeHandlerRegistration = Window.addResizeHandler(new ResizeHandler() {
@Override
public void onResize(ResizeEvent event) {
windowWidth = event.getWidth();
}
});
super.show();
}
@Override
public void toback() {
// System.out.println("to back");
getElement().getStyle().setZIndex(1);
}
@Override
public void toFront() {
// System.out.println("to front");
getElement().getStyle().setZIndex(100);
}
@Override
public void setResizable(Boolean resizable) {
isResizable = resizable;
}
}