From: Johannes Dahlström Date: Mon, 11 Jun 2012 08:49:21 +0000 (+0300) Subject: Merge commit '295e0' X-Git-Tag: 7.0.0.alpha3~188 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0509cb5bd5a5e16facf42b05f91c740cb19b11f7;p=vaadin-framework.git Merge commit '295e0' --- 0509cb5bd5a5e16facf42b05f91c740cb19b11f7 diff --cc src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java index 0d222044ba,0000000000..fb853b8a55 mode 100644,000000..100644 --- a/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java +++ b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java @@@ -1,438 -1,0 +1,457 @@@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.notification; + +import java.util.ArrayList; +import java.util.Date; +import java.util.EventObject; +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.KeyCodes; +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.Timer; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.root.VRoot; + +public class VNotification extends VOverlay { + + public static final int CENTERED = 1; + public static final int CENTERED_TOP = 2; + public static final int CENTERED_BOTTOM = 3; + public static final int TOP_LEFT = 4; + public static final int TOP_RIGHT = 5; + public static final int BOTTOM_LEFT = 6; + public static final int BOTTOM_RIGHT = 7; + + public static final int DELAY_FOREVER = -1; + public static final int DELAY_NONE = 0; + + private static final String STYLENAME = "v-Notification"; + private static final int mouseMoveThreshold = 7; + private static final int Z_INDEX_BASE = 20000; + public static final String STYLE_SYSTEM = "system"; + private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps + ++ private static final ArrayList notifications = new ArrayList(); ++ + private int startOpacity = 90; + private int fadeMsec = 400; + private int delayMsec = 1000; + + private Timer fader; + private Timer delay; + + private int x = -1; + private int y = -1; + + private String temporaryStyle; + + private ArrayList listeners; + private static final int TOUCH_DEVICE_IDLE_DELAY = 1000; + + public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style"; + public static final String ATTRIBUTE_NOTIFICATION_CAPTION = "caption"; + public static final String ATTRIBUTE_NOTIFICATION_MESSAGE = "message"; + public static final String ATTRIBUTE_NOTIFICATION_ICON = "icon"; + public static final String ATTRIBUTE_NOTIFICATION_POSITION = "position"; + public static final String ATTRIBUTE_NOTIFICATION_DELAY = "delay"; + + /** + * Default constructor. You should use GWT.create instead. + */ + public VNotification() { + setStyleName(STYLENAME); + sinkEvents(Event.ONCLICK); + DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE); + } + + /** + * @deprecated Use static {@link #createNotification(int)} instead to enable + * GWT deferred binding. + * + * @param delayMsec + */ + @Deprecated + public VNotification(int delayMsec) { + this(); + this.delayMsec = delayMsec; + if (BrowserInfo.get().isTouchDevice()) { + new Timer() { + @Override + public void run() { + if (isAttached()) { + fade(); + } + } + }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); + } + } + + /** + * @deprecated Use static {@link #createNotification(int, int, int)} instead + * to enable GWT deferred binding. + * + * @param delayMsec + * @param fadeMsec + * @param startOpacity + */ + @Deprecated + public VNotification(int delayMsec, int fadeMsec, int startOpacity) { + this(delayMsec); + this.fadeMsec = fadeMsec; + this.startOpacity = startOpacity; + } + + public void startDelay() { + DOM.removeEventPreview(this); + if (delayMsec > 0) { + if (delay == null) { + delay = new Timer() { + @Override + public void run() { + fade(); + } + }; + delay.schedule(delayMsec); + } + } else if (delayMsec == 0) { + fade(); + } + } + + @Override + public void show() { + show(CENTERED); + } + + public void show(String style) { + show(CENTERED, style); + } + + public void show(int position) { + show(position, null); + } + + public void show(Widget widget, int position, String style) { + setWidget(widget); + show(position, style); + } + + public void show(String html, int position, String style) { + setWidget(new HTML(html)); + show(position, style); + } + + public void show(int position, String style) { + setOpacity(getElement(), startOpacity); + if (style != null) { + temporaryStyle = style; + addStyleName(style); + addStyleDependentName(style); + } + super.show(); ++ notifications.add(this); + setPosition(position); + /** + * Android 4 fails to render notifications correctly without a little + * nudge (#8551) + */ + if (BrowserInfo.get().isAndroid()) { + Util.setStyleTemporarily(getElement(), "display", "none"); + } + } + + @Override + public void hide() { + DOM.removeEventPreview(this); + cancelDelay(); + cancelFade(); + if (temporaryStyle != null) { + removeStyleName(temporaryStyle); + removeStyleDependentName(temporaryStyle); + temporaryStyle = null; + } + super.hide(); ++ notifications.remove(this); + fireEvent(new HideEvent(this)); + } + + public void fade() { + DOM.removeEventPreview(this); + cancelDelay(); + if (fader == null) { + fader = new Timer() { + private final long start = new Date().getTime(); + + @Override + public void run() { + /* + * To make animation smooth, don't count that event happens + * on time. Reduce opacity according to the actual time + * spent instead of fixed decrement. + */ + long now = new Date().getTime(); + long timeEplaced = now - start; + float remainingFraction = 1 - timeEplaced + / (float) fadeMsec; + int opacity = (int) (startOpacity * remainingFraction); + if (opacity <= 0) { + cancel(); + hide(); + if (BrowserInfo.get().isOpera()) { + // tray notification on opera needs to explicitly + // define + // size, reset it + DOM.setStyleAttribute(getElement(), "width", ""); + DOM.setStyleAttribute(getElement(), "height", ""); + } + } else { + setOpacity(getElement(), opacity); + } + } + }; + fader.scheduleRepeating(FADE_ANIMATION_INTERVAL); + } + } + + public void setPosition(int position) { + final Element el = getElement(); + DOM.setStyleAttribute(el, "top", ""); + DOM.setStyleAttribute(el, "left", ""); + DOM.setStyleAttribute(el, "bottom", ""); + DOM.setStyleAttribute(el, "right", ""); + switch (position) { + case TOP_LEFT: + DOM.setStyleAttribute(el, "top", "0px"); + DOM.setStyleAttribute(el, "left", "0px"); + break; + case TOP_RIGHT: + DOM.setStyleAttribute(el, "top", "0px"); + DOM.setStyleAttribute(el, "right", "0px"); + break; + case BOTTOM_RIGHT: + DOM.setStyleAttribute(el, "position", "absolute"); + if (BrowserInfo.get().isOpera()) { + // tray notification on opera needs explicitly defined size + DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px"); + DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px"); + } + DOM.setStyleAttribute(el, "bottom", "0px"); + DOM.setStyleAttribute(el, "right", "0px"); + break; + case BOTTOM_LEFT: + DOM.setStyleAttribute(el, "bottom", "0px"); + DOM.setStyleAttribute(el, "left", "0px"); + break; + case CENTERED_TOP: + center(); + DOM.setStyleAttribute(el, "top", "0px"); + break; + case CENTERED_BOTTOM: + center(); + DOM.setStyleAttribute(el, "top", ""); + DOM.setStyleAttribute(el, "bottom", "0px"); + break; + default: + case CENTERED: + center(); + break; + } + } + + private void cancelFade() { + if (fader != null) { + fader.cancel(); + fader = null; + } + } + + private void cancelDelay() { + if (delay != null) { + delay.cancel(); + delay = null; + } + } + + private void setOpacity(Element el, int opacity) { + DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0)); + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity + + ")"); + } + } + + @Override + public void onBrowserEvent(Event event) { + DOM.removeEventPreview(this); + if (fader == null) { + fade(); + } + } + + @Override + public boolean onEventPreview(Event event) { + int type = DOM.eventGetType(event); + // "modal" + if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) { + if (type == Event.ONCLICK) { + if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { + fade(); + return false; + } + } else if (type == Event.ONKEYDOWN + && event.getKeyCode() == KeyCodes.KEY_ESCAPE) { + fade(); + return false; + } + if (temporaryStyle == STYLE_SYSTEM) { + return true; + } else { + return false; + } + } + // default + switch (type) { + case Event.ONMOUSEMOVE: + + if (x < 0) { + x = DOM.eventGetClientX(event); + y = DOM.eventGetClientY(event); + } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold + || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) { + startDelay(); + } + break; + case Event.ONMOUSEDOWN: + case Event.ONMOUSEWHEEL: + case Event.ONSCROLL: + startDelay(); + break; + case Event.ONKEYDOWN: + if (event.getRepeat()) { + return true; + } + startDelay(); + break; + default: + break; + } + return true; + } + + public void addEventListener(EventListener listener) { + if (listeners == null) { + listeners = new ArrayList(); + } + listeners.add(listener); + } + + public void removeEventListener(EventListener listener) { + if (listeners == null) { + return; + } + listeners.remove(listener); + } + + private void fireEvent(HideEvent event) { + if (listeners != null) { + for (Iterator it = listeners.iterator(); it + .hasNext();) { + EventListener l = it.next(); + l.notificationHidden(event); + } + } + } + + public static void showNotification(ApplicationConnection client, + final UIDL notification) { + boolean onlyPlainText = notification + .hasAttribute(VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); + String html = ""; + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_ICON)) { + final String parsedUri = client.translateVaadinUri(notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_ICON)); + html += ""; + } + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_CAPTION)) { + String caption = notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_CAPTION); + if (onlyPlainText) { + caption = Util.escapeHTML(caption); + caption = caption.replaceAll("\\n", "
"); + } + html += "

" + caption + "

"; + } + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE)) { + String message = notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE); + if (onlyPlainText) { + message = Util.escapeHTML(message); + message = message.replaceAll("\\n", "
"); + } + html += "

" + message + "

"; + } + + final String style = notification + .hasAttribute(ATTRIBUTE_NOTIFICATION_STYLE) ? notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_STYLE) : null; + final int position = notification + .getIntAttribute(ATTRIBUTE_NOTIFICATION_POSITION); + final int delay = notification + .getIntAttribute(ATTRIBUTE_NOTIFICATION_DELAY); + createNotification(delay).show(html, position, style); + } + + public static VNotification createNotification(int delayMsec) { + final VNotification notification = GWT.create(VNotification.class); + notification.delayMsec = delayMsec; + if (BrowserInfo.get().isTouchDevice()) { + new Timer() { + @Override + public void run() { + if (notification.isAttached()) { + notification.fade(); + } + } + }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY); + } + return notification; + } + + public class HideEvent extends EventObject { + + public HideEvent(Object source) { + super(source); + } + } + + public interface EventListener extends java.util.EventListener { + public void notificationHidden(HideEvent event); + } ++ ++ /** ++ * Moves currently visible notifications to the top of the event preview ++ * stack. Can be called when opening other overlays such as subwindows to ++ * ensure the notifications receive the events they need and don't linger ++ * indefinitely. See #7136. ++ * ++ * TODO Should this be a generic Overlay feature instead? ++ */ ++ public static void bringNotificationsToFront() { ++ for (VNotification notification : notifications) { ++ DOM.removeEventPreview(notification); ++ DOM.addEventPreview(notification); ++ } ++ } +} diff --cc src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java index d08387fc6d,0000000000..aa93195cb9 mode 100644,000000..100644 --- a/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java +++ b/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java @@@ -1,912 -1,0 +1,913 @@@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.window; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.ScrollEvent; +import com.google.gwt.event.dom.client.ScrollHandler; +import com.google.gwt.user.client.Command; +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.Window; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.Console; +import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.FocusableScrollPanel; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.VOverlay; + +/** + * "Sub window" component. + * + * @author Vaadin Ltd + */ +public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, + ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable { + + /** + * Minimum allowed height of a window. This refers to the content area, not + * the outer borders. + */ + private static final int MIN_CONTENT_AREA_HEIGHT = 100; + + /** + * Minimum allowed width of a window. This refers to the content area, not + * the outer borders. + */ + private static final int MIN_CONTENT_AREA_WIDTH = 150; + + private static ArrayList windowOrder = new ArrayList(); + + private static boolean orderingDefered; + + public static final String CLASSNAME = "v-window"; + + private static final int STACKING_OFFSET_PIXELS = 15; + + public static final int Z_INDEX = 10000; + + ComponentConnector layout; + + Element contents; + + Element header; + + Element footer; + + private Element resizeBox; + + final FocusableScrollPanel contentPanel = new FocusableScrollPanel(); + + private boolean dragging; + + private int startX; + + private int startY; + + private int origX; + + private int origY; + + private boolean resizing; + + private int origW; + + private int origH; + + Element closeBox; + + protected ApplicationConnection client; + + String id; + + ShortcutActionHandler shortcutHandler; + + /** Last known positionx read from UIDL or updated to application connection */ + private int uidlPositionX = -1; + + /** Last known positiony read from UIDL or updated to application connection */ + private int uidlPositionY = -1; + + boolean vaadinModality = false; + + boolean resizable = true; + + private boolean draggable = true; + + boolean resizeLazy = false; + + private Element modalityCurtain; + private Element draggingCurtain; + private Element resizingCurtain; + + private Element headerText; + + private boolean closable = true; + + // If centered (via UIDL), the window should stay in the centered -mode + // until a position is received from the server, or the user moves or + // resizes the window. + boolean centered = false; + + boolean immediate; + + private Element wrapper; + + boolean visibilityChangesDisabled; + + int bringToFrontSequence = -1; + + private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200, + new ScheduledCommand() { + + public void execute() { + updateContentsSize(); + } + }); + + public VWindow() { + super(false, false, true); // no autohide, not modal, shadow + // Different style of shadow for windows + setShadowStyle("window"); + + constructDOM(); + contentPanel.addScrollHandler(this); + contentPanel.addKeyDownHandler(this); + contentPanel.addFocusHandler(this); + contentPanel.addBlurHandler(this); + } + + public void bringToFront() { + int curIndex = windowOrder.indexOf(this); + if (curIndex + 1 < windowOrder.size()) { + windowOrder.remove(this); + windowOrder.add(this); + for (; curIndex < windowOrder.size(); curIndex++) { + windowOrder.get(curIndex).setWindowOrder(curIndex); + } + } + } + + /** + * Returns true if this window is the topmost VWindow + * + * @return + */ + private boolean isActive() { + return equals(getTopmostWindow()); + } + + private static VWindow getTopmostWindow() { + return windowOrder.get(windowOrder.size() - 1); + } + + void setWindowOrderAndPosition() { + // This cannot be done in the constructor as the widgets are created in + // a different order than on they should appear on screen + if (windowOrder.contains(this)) { + // Already set + return; + } + final int order = windowOrder.size(); + setWindowOrder(order); + windowOrder.add(this); + setPopupPosition(order * STACKING_OFFSET_PIXELS, order + * STACKING_OFFSET_PIXELS); + + } + + private void setWindowOrder(int order) { + setZIndex(order + Z_INDEX); + } + + @Override + protected void setZIndex(int zIndex) { + super.setZIndex(zIndex); + if (vaadinModality) { + DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + zIndex); + } + } + + protected Element getModalityCurtain() { + if (modalityCurtain == null) { + modalityCurtain = DOM.createDiv(); + modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain"); + } + return modalityCurtain; + } + + protected void constructDOM() { + setStyleName(CLASSNAME); + + header = DOM.createDiv(); + DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader"); + headerText = DOM.createDiv(); + DOM.setElementProperty(headerText, "className", CLASSNAME + "-header"); + contents = DOM.createDiv(); + DOM.setElementProperty(contents, "className", CLASSNAME + "-contents"); + footer = DOM.createDiv(); + DOM.setElementProperty(footer, "className", CLASSNAME + "-footer"); + resizeBox = DOM.createDiv(); + DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox"); + closeBox = DOM.createDiv(); + DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox"); + DOM.appendChild(footer, resizeBox); + + wrapper = DOM.createDiv(); + DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap"); + + DOM.appendChild(wrapper, header); + DOM.appendChild(wrapper, closeBox); + DOM.appendChild(header, headerText); + DOM.appendChild(wrapper, contents); + DOM.appendChild(wrapper, footer); + DOM.appendChild(super.getContainerElement(), wrapper); + + sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK + | Event.ONLOSECAPTURE); + + setWidget(contentPanel); + + } + + /** + * Calling this method will defer ordering algorithm, to order windows based + * on servers bringToFront and modality instructions. Non changed windows + * will be left intact. + */ + static void deferOrdering() { + if (!orderingDefered) { + orderingDefered = true; + Scheduler.get().scheduleFinally(new Command() { + public void execute() { + doServerSideOrdering(); ++ VNotification.bringNotificationsToFront(); + } + }); + } + } + + private static void doServerSideOrdering() { + orderingDefered = false; + VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]); + Arrays.sort(array, new Comparator() { + public int compare(VWindow o1, VWindow o2) { + /* + * Order by modality, then by bringtofront sequence. + */ + + if (o1.vaadinModality && !o2.vaadinModality) { + return 1; + } else if (!o1.vaadinModality && o2.vaadinModality) { + return -1; + } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) { + return 1; + } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) { + return -1; + } else { + return 0; + } + } + }); + for (int i = 0; i < array.length; i++) { + VWindow w = array[i]; + if (w.bringToFrontSequence != -1 || w.vaadinModality) { + w.bringToFront(); + w.bringToFrontSequence = -1; + } + } + } + + @Override + public void setVisible(boolean visible) { + /* + * Visibility with VWindow works differently than with other Paintables + * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is + * used to avoid visibility call from + * ApplicationConnection.updateComponent(); + */ + if (!visibilityChangesDisabled) { + super.setVisible(visible); + } + } + + void setDraggable(boolean draggable) { + if (this.draggable == draggable) { + return; + } + + this.draggable = draggable; + + setCursorProperties(); + } + + private void setCursorProperties() { + if (!draggable) { + header.getStyle().setProperty("cursor", "default"); + footer.getStyle().setProperty("cursor", "default"); + } else { + header.getStyle().setProperty("cursor", ""); + footer.getStyle().setProperty("cursor", ""); + } + } + + /** + * Sets the closable state of the window. Additionally hides/shows the close + * button according to the new state. + * + * @param closable + * true if the window can be closed by the user + */ + protected void setClosable(boolean closable) { + if (this.closable == closable) { + return; + } + + this.closable = closable; + if (closable) { + DOM.setStyleAttribute(closeBox, "display", ""); + } else { + DOM.setStyleAttribute(closeBox, "display", "none"); + } + + } + + /** + * Returns the closable state of the sub window. If the sub window is + * closable a decoration (typically an X) is shown to the user. By clicking + * on the X the user can close the window. + * + * @return true if the sub window is closable + */ + protected boolean isClosable() { + return closable; + } + + @Override + public void show() { + if (!windowOrder.contains(this)) { + // This is needed if the window is hidden and then shown again. + // Otherwise this VWindow is added to windowOrder in the + // constructor. + windowOrder.add(this); + } + + if (vaadinModality) { + showModalityCurtain(); + } + super.show(); + } + + @Override + public void hide() { + if (vaadinModality) { + hideModalityCurtain(); + } + super.hide(); + + // Remove window from windowOrder to avoid references being left + // hanging. + windowOrder.remove(this); + } + + void setVaadinModality(boolean modality) { + vaadinModality = modality; + if (vaadinModality) { + if (isAttached()) { + showModalityCurtain(); + } + deferOrdering(); + } else { + if (modalityCurtain != null) { + if (isAttached()) { + hideModalityCurtain(); + } + modalityCurtain = null; + } + } + } + + private void showModalityCurtain() { + DOM.setStyleAttribute(getModalityCurtain(), "zIndex", + "" + (windowOrder.indexOf(this) + Z_INDEX)); + if (isShowing()) { + RootPanel.getBodyElement().insertBefore(getModalityCurtain(), + getElement()); + } else { + DOM.appendChild(RootPanel.getBodyElement(), getModalityCurtain()); + } + } + + private void hideModalityCurtain() { + DOM.removeChild(RootPanel.getBodyElement(), modalityCurtain); + } + + /* + * Shows an empty div on top of all other content; used when moving, so that + * iframes (etc) do not steal event. + */ + private void showDraggingCurtain() { + DOM.appendChild(RootPanel.getBodyElement(), getDraggingCurtain()); + } + + private void hideDraggingCurtain() { + if (draggingCurtain != null) { + DOM.removeChild(RootPanel.getBodyElement(), draggingCurtain); + } + } + + /* + * Shows an empty div on top of all other content; used when resizing, so + * that iframes (etc) do not steal event. + */ + private void showResizingCurtain() { + DOM.appendChild(RootPanel.getBodyElement(), getResizingCurtain()); + } + + private void hideResizingCurtain() { + if (resizingCurtain != null) { + DOM.removeChild(RootPanel.getBodyElement(), resizingCurtain); + } + } + + private Element getDraggingCurtain() { + if (draggingCurtain == null) { + draggingCurtain = createCurtain(); + draggingCurtain.setClassName(CLASSNAME + "-draggingCurtain"); + } + + return draggingCurtain; + } + + private Element getResizingCurtain() { + if (resizingCurtain == null) { + resizingCurtain = createCurtain(); + resizingCurtain.setClassName(CLASSNAME + "-resizingCurtain"); + } + + return resizingCurtain; + } + + private Element createCurtain() { + Element curtain = DOM.createDiv(); + + DOM.setStyleAttribute(curtain, "position", "absolute"); + DOM.setStyleAttribute(curtain, "top", "0px"); + DOM.setStyleAttribute(curtain, "left", "0px"); + DOM.setStyleAttribute(curtain, "width", "100%"); + DOM.setStyleAttribute(curtain, "height", "100%"); + DOM.setStyleAttribute(curtain, "zIndex", "" + VOverlay.Z_INDEX); + + return curtain; + } + + void setResizable(boolean resizability) { + resizable = resizability; + if (resizability) { + DOM.setElementProperty(footer, "className", CLASSNAME + "-footer"); + DOM.setElementProperty(resizeBox, "className", CLASSNAME + + "-resizebox"); + } else { + DOM.setElementProperty(footer, "className", CLASSNAME + "-footer " + + CLASSNAME + "-footer-noresize"); + DOM.setElementProperty(resizeBox, "className", CLASSNAME + + "-resizebox " + CLASSNAME + "-resizebox-disabled"); + } + } + + @Override + public void setPopupPosition(int left, int top) { + if (top < 0) { + // ensure window is not moved out of browser window from top of the + // screen + top = 0; + } + super.setPopupPosition(left, top); + if (left != uidlPositionX && client != null) { + client.updateVariable(id, "positionx", left, false); + uidlPositionX = left; + } + if (top != uidlPositionY && client != null) { + client.updateVariable(id, "positiony", top, false); + uidlPositionY = top; + } + } + + public void setCaption(String c) { + setCaption(c, null); + } + + public void setCaption(String c, String icon) { + String html = Util.escapeHTML(c); + if (icon != null) { + icon = client.translateVaadinUri(icon); + html = "" + html; + } + DOM.setInnerHTML(headerText, html); + } + + @Override + protected Element getContainerElement() { + // in GWT 1.5 this method is used in PopupPanel constructor + if (contents == null) { + return super.getContainerElement(); + } + return contents; + } + + @Override + public void onBrowserEvent(final Event event) { + boolean bubble = true; + + final int type = event.getTypeInt(); + + final Element target = DOM.eventGetTarget(event); + + if (client != null && header.isOrHasChild(target)) { + // Handle window caption tooltips + client.handleTooltipEvent(event, this); + } + + if (resizing || resizeBox == target) { + onResizeEvent(event); + bubble = false; + } else if (isClosable() && target == closeBox) { + if (type == Event.ONCLICK) { + onCloseClick(); + } + bubble = false; + } else if (dragging || !contents.isOrHasChild(target)) { + onDragEvent(event); + bubble = false; + } else if (type == Event.ONCLICK) { + // clicked inside window, ensure to be on top + if (!isActive()) { + bringToFront(); + } + } + + /* + * If clicking on other than the content, move focus to the window. + * After that this windows e.g. gets all keyboard shortcuts. + */ + if (type == Event.ONMOUSEDOWN + && !contentPanel.getElement().isOrHasChild(target) + && target != closeBox) { + contentPanel.focus(); + } + + if (!bubble) { + event.stopPropagation(); + } else { + // Super.onBrowserEvent takes care of Handlers added by the + // ClickEventHandler + super.onBrowserEvent(event); + } + } + + private void onCloseClick() { + client.updateVariable(id, "close", true, true); + } + + private void onResizeEvent(Event event) { + if (resizable && Util.isTouchEventOrLeftMouseButton(event)) { + switch (event.getTypeInt()) { + case Event.ONMOUSEDOWN: + case Event.ONTOUCHSTART: + if (!isActive()) { + bringToFront(); + } + showResizingCurtain(); + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(resizeBox, "visibility", "hidden"); + } + resizing = true; + startX = Util.getTouchOrMouseClientX(event); + startY = Util.getTouchOrMouseClientY(event); + origW = getElement().getOffsetWidth(); + origH = getElement().getOffsetHeight(); + DOM.setCapture(getElement()); + event.preventDefault(); + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + setSize(event, true); + case Event.ONTOUCHCANCEL: + DOM.releaseCapture(getElement()); + case Event.ONLOSECAPTURE: + hideResizingCurtain(); + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(resizeBox, "visibility", ""); + } + resizing = false; + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + if (resizing) { + centered = false; + setSize(event, false); + event.preventDefault(); + } + break; + default: + event.preventDefault(); + break; + } + } + } + + /** + * TODO check if we need to support this with touch based devices. + * + * Checks if the cursor was inside the browser content area when the event + * happened. + * + * @param event + * The event to be checked + * @return true, if the cursor is inside the browser content area + * + * false, otherwise + */ + private boolean cursorInsideBrowserContentArea(Event event) { + if (event.getClientX() < 0 || event.getClientY() < 0) { + // Outside to the left or above + return false; + } + + if (event.getClientX() > Window.getClientWidth() + || event.getClientY() > Window.getClientHeight()) { + // Outside to the right or below + return false; + } + + return true; + } + + private void setSize(Event event, boolean updateVariables) { + if (!cursorInsideBrowserContentArea(event)) { + // Only drag while cursor is inside the browser client area + return; + } + + int w = Util.getTouchOrMouseClientX(event) - startX + origW; + int minWidth = getMinWidth(); + if (w < minWidth) { + w = minWidth; + } + + int h = Util.getTouchOrMouseClientY(event) - startY + origH; + int minHeight = getMinHeight(); + if (h < minHeight) { + h = minHeight; + } + + setWidth(w + "px"); + setHeight(h + "px"); + + if (updateVariables) { + // sending width back always as pixels, no need for unit + client.updateVariable(id, "width", w, false); + client.updateVariable(id, "height", h, immediate); + } + + if (updateVariables || !resizeLazy) { + // Resize has finished or is not lazy + updateContentsSize(); + } else { + // Lazy resize - wait for a while before re-rendering contents + delayedContentsSizeUpdater.trigger(); + } + } + + private void updateContentsSize() { + // Update child widget dimensions + if (client != null) { + client.handleComponentRelativeSize(layout.getWidget()); + client.runDescendentsLayout((HasWidgets) layout.getWidget()); + } + + LayoutManager layoutManager = LayoutManager.get(client); + layoutManager.setNeedsMeasure(ConnectorMap.get(client).getConnector( + this)); + layoutManager.layoutNow(); + } + + @Override + public void setWidth(String width) { + // Override PopupPanel which sets the width to the contents + getElement().getStyle().setProperty("width", width); + // Update v-has-width in case undefined window is resized + setStyleName("v-has-width", width != null && width.length() > 0); + } + + @Override + public void setHeight(String height) { + // Override PopupPanel which sets the height to the contents + getElement().getStyle().setProperty("height", height); + // Update v-has-height in case undefined window is resized + setStyleName("v-has-height", height != null && height.length() > 0); + } + + private void onDragEvent(Event event) { + if (!Util.isTouchEventOrLeftMouseButton(event)) { + return; + } + + switch (DOM.eventGetType(event)) { + case Event.ONTOUCHSTART: + if (event.getTouches().length() > 1) { + return; + } + case Event.ONMOUSEDOWN: + if (!isActive()) { + bringToFront(); + } + beginMovingWindow(event); + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + case Event.ONLOSECAPTURE: + stopMovingWindow(); + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + moveWindow(event); + break; + default: + break; + } + } + + private void moveWindow(Event event) { + if (dragging) { + centered = false; + if (cursorInsideBrowserContentArea(event)) { + // Only drag while cursor is inside the browser client area + final int x = Util.getTouchOrMouseClientX(event) - startX + + origX; + final int y = Util.getTouchOrMouseClientY(event) - startY + + origY; + setPopupPosition(x, y); + } + DOM.eventPreventDefault(event); + } + } + + private void beginMovingWindow(Event event) { + if (draggable) { + showDraggingCurtain(); + dragging = true; + startX = Util.getTouchOrMouseClientX(event); + startY = Util.getTouchOrMouseClientY(event); + origX = DOM.getAbsoluteLeft(getElement()); + origY = DOM.getAbsoluteTop(getElement()); + DOM.setCapture(getElement()); + DOM.eventPreventDefault(event); + } + } + + private void stopMovingWindow() { + dragging = false; + hideDraggingCurtain(); + DOM.releaseCapture(getElement()); + } + + @Override + public boolean onEventPreview(Event event) { + if (dragging) { + onDragEvent(event); + return false; + } else if (resizing) { + onResizeEvent(event); + return false; + } + + // TODO This is probably completely unnecessary as the modality curtain + // prevents events from reaching other windows and any security check + // must be done on the server side and not here. + // The code here is also run many times as each VWindow has an event + // preview but we cannot check only the current VWindow here (e.g. + // if(isTopMost) {...}) because PopupPanel will cause all events that + // are not cancelled here and target this window to be consume():d + // meaning the event won't be sent to the rest of the preview handlers. + + if (getTopmostWindow().vaadinModality) { + // Topmost window is modal. Cancel the event if it targets something + // outside that window (except debug console...) + if (DOM.getCaptureElement() != null) { + // Allow events when capture is set + return true; + } + + final Element target = event.getEventTarget().cast(); + if (!DOM.isOrHasChild(getTopmostWindow().getElement(), target)) { + // not within the modal window, but let's see if it's in the + // debug window + Widget w = Util.findWidget(target, null); + while (w != null) { + if (w instanceof Console) { + return true; // allow debug-window clicks + } else if (ConnectorMap.get(client).isConnector(w)) { + return false; + } + w = w.getParent(); + } + return false; + } + } + return true; + } + + @Override + public void addStyleDependentName(String styleSuffix) { + // VWindow's getStyleElement() does not return the same element as + // getElement(), so we need to override this. + setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix, + true); + } + + public ShortcutActionHandler getShortcutActionHandler() { + return shortcutHandler; + } + + public void onScroll(ScrollEvent event) { + client.updateVariable(id, "scrollTop", + contentPanel.getScrollPosition(), false); + client.updateVariable(id, "scrollLeft", + contentPanel.getHorizontalScrollPosition(), false); + + } + + public void onKeyDown(KeyDownEvent event) { + if (shortcutHandler != null) { + shortcutHandler + .handleKeyboardEvent(Event.as(event.getNativeEvent())); + return; + } + } + + public void onBlur(BlurEvent event) { + if (client.hasEventListeners(this, EventId.BLUR)) { + client.updateVariable(id, EventId.BLUR, "", true); + } + } + + public void onFocus(FocusEvent event) { + if (client.hasEventListeners(this, EventId.FOCUS)) { + client.updateVariable(id, EventId.FOCUS, "", true); + } + } + + public void focus() { + contentPanel.focus(); + } + + public int getMinHeight() { + return MIN_CONTENT_AREA_HEIGHT + getDecorationHeight(); + } + + private int getDecorationHeight() { + LayoutManager lm = layout.getLayoutManager(); + int headerHeight = lm.getOuterHeight(header); + int footerHeight = lm.getOuterHeight(footer); + return headerHeight + footerHeight; + } + + public int getMinWidth() { + return MIN_CONTENT_AREA_WIDTH + getDecorationWidth(); + } + + private int getDecorationWidth() { + LayoutManager layoutManager = layout.getLayoutManager(); + return layoutManager.getOuterWidth(getElement()) + - contentPanel.getElement().getOffsetWidth(); + } + +}