From 8ba529503b6caee2d7755f292fb1f9f985943e78 Mon Sep 17 00:00:00 2001 From: Denis Anisimov Date: Wed, 23 Nov 2016 20:11:33 +0300 Subject: [PATCH] Provide access to window order position in windows stack (#14325). Change-Id: I259c659987b5b15b354e16d0be1523f4ede809f0 --- .../java/com/vaadin/client/ui/VWindow.java | 72 ++++++- .../com/vaadin/client/ui/ui/UIConnector.java | 84 ++++++++ .../client/ui/window/WindowConnector.java | 6 +- .../client/ui/window/WindowOrderEvent.java | 74 +++++++ .../client/ui/window/WindowOrderHandler.java | 36 ++++ server/src/main/java/com/vaadin/ui/UI.java | 122 ++++++++++++ .../src/main/java/com/vaadin/ui/Window.java | 119 ++++++++++++ .../main/java/com/vaadin/shared/EventId.java | 1 + .../com/vaadin/shared/ui/WindowOrderRpc.java | 41 ++++ .../tests/components/window/WindowOrder.java | 175 +++++++++++++++++ .../components/window/WindowOrderTest.java | 182 ++++++++++++++++++ 11 files changed, 900 insertions(+), 12 deletions(-) create mode 100644 client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java create mode 100644 shared/src/main/java/com/vaadin/shared/ui/WindowOrderRpc.java create mode 100644 uitest/src/main/java/com/vaadin/tests/components/window/WindowOrder.java create mode 100644 uitest/src/test/java/com/vaadin/tests/components/window/WindowOrderTest.java diff --git a/client/src/main/java/com/vaadin/client/ui/VWindow.java b/client/src/main/java/com/vaadin/client/ui/VWindow.java index 8f1292ca1d..5a316933ff 100644 --- a/client/src/main/java/com/vaadin/client/ui/VWindow.java +++ b/client/src/main/java/com/vaadin/client/ui/VWindow.java @@ -44,6 +44,7 @@ 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.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; @@ -64,6 +65,8 @@ import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.client.ui.window.WindowMoveEvent; import com.vaadin.client.ui.window.WindowMoveHandler; +import com.vaadin.client.ui.window.WindowOrderEvent; +import com.vaadin.client.ui.window.WindowOrderHandler; import com.vaadin.shared.Connector; import com.vaadin.shared.EventId; import com.vaadin.shared.ui.window.WindowMode; @@ -79,6 +82,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, private static ArrayList windowOrder = new ArrayList<>(); + private static HandlerManager WINDOW_ORDER_HANDLER = new HandlerManager( + VWindow.class); + private static boolean orderingDefered; public static final String CLASSNAME = "v-window"; @@ -278,14 +284,37 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } public void bringToFront() { - int curIndex = windowOrder.indexOf(this); + bringToFront(true); + } + + private void bringToFront(boolean notifyListeners) { + int curIndex = getWindowOrder(); if (curIndex + 1 < windowOrder.size()) { windowOrder.remove(this); windowOrder.add(this); for (; curIndex < windowOrder.size(); curIndex++) { - windowOrder.get(curIndex).setWindowOrder(curIndex); + VWindow window = windowOrder.get(curIndex); + window.setWindowOrder(curIndex); } } + if (notifyListeners) { + fireOrderEvent(); + } + } + + static void fireOrderEvent() { + fireOrderEvent(windowOrder); + } + + private void doFireOrderEvent() { + ArrayList list = new ArrayList<>(); + list.add(this); + fireOrderEvent(list); + } + + private static void fireOrderEvent(ArrayList windows) { + WINDOW_ORDER_HANDLER + .fireEvent(new WindowOrderEvent(new ArrayList<>(windows))); } /** @@ -317,13 +346,22 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, windowOrder.add(this); setPopupPosition(order * STACKING_OFFSET_PIXELS, order * STACKING_OFFSET_PIXELS); - + doFireOrderEvent(); } private void setWindowOrder(int order) { setZIndex(order + Z_INDEX); } + /** + * Returns window position in list of opened and shown windows. + * + * @since 8.0.0 + */ + public final int getWindowOrder() { + return windowOrder.indexOf(this); + } + @Override protected void setZIndex(int zIndex) { super.setZIndex(zIndex); @@ -536,7 +574,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, for (int i = 0; i < array.length; i++) { VWindow w = array[i]; if (w.bringToFrontSequence != -1 || w.vaadinModality) { - w.bringToFront(); + w.bringToFront(false); w.bringToFrontSequence = -1; } } @@ -548,6 +586,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, if (topmost != null && topmost.vaadinModality) { topmost.focus(); } + fireOrderEvent(); } @Override @@ -655,15 +694,21 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } super.hide(); - int curIndex = windowOrder.indexOf(this); + int curIndex = getWindowOrder(); // Remove window from windowOrder to avoid references being left // hanging. windowOrder.remove(curIndex); // Update the z-indices of any remaining windows + ArrayList update = new ArrayList<>( + windowOrder.size() - curIndex + 1); + update.add(this); while (curIndex < windowOrder.size()) { - windowOrder.get(curIndex).setWindowOrder(curIndex++); + VWindow window = windowOrder.get(curIndex); + window.setWindowOrder(curIndex++); + update.add(window); } focusTopmostModalWindow(); + fireOrderEvent(update); } /** For internal use only. May be removed or replaced in the future. */ @@ -689,8 +734,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } private void showModalityCurtain() { - getModalityCurtain().getStyle() - .setZIndex(windowOrder.indexOf(this) + Z_INDEX); + getModalityCurtain().getStyle().setZIndex(getWindowOrder() + Z_INDEX); if (isShowing()) { getOverlayContainer().insertBefore(getModalityCurtain(), @@ -1463,4 +1507,16 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, return addHandler(handler, WindowMoveEvent.getType()); } + /** + * Adds a Handler for window order change event. + * + * @since 8.0.0 + * + * @return registration object to deregister the handler + */ + public static HandlerRegistration addWindowOrderHandler( + WindowOrderHandler handler) { + return WINDOW_ORDER_HANDLER.addHandler(WindowOrderEvent.getType(), + handler); + } } diff --git a/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java b/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java index af212c16f4..677abbbecb 100644 --- a/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java @@ -16,6 +16,11 @@ package com.vaadin.client.ui.ui; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; @@ -75,14 +80,19 @@ import com.vaadin.client.ui.VUI; import com.vaadin.client.ui.VWindow; import com.vaadin.client.ui.layout.MayScrollChildren; import com.vaadin.client.ui.window.WindowConnector; +import com.vaadin.client.ui.window.WindowOrderEvent; +import com.vaadin.client.ui.window.WindowOrderHandler; import com.vaadin.server.Page.Styles; import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.Connector; +import com.vaadin.shared.EventId; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.Version; import com.vaadin.shared.communication.MethodInvocation; import com.vaadin.shared.ui.ComponentStateUtil; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.WindowOrderRpc; import com.vaadin.shared.ui.ui.DebugWindowClientRpc; import com.vaadin.shared.ui.ui.DebugWindowServerRpc; import com.vaadin.shared.ui.ui.PageClientRpc; @@ -103,6 +113,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector private String activeTheme = null; + private HandlerRegistration windowOrderRegistration; + private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() { @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { @@ -112,9 +124,33 @@ public class UIConnector extends AbstractSingleComponentContainerConnector } }; + private WindowOrderHandler windowOrderHandler = new WindowOrderHandler() { + + @Override + public void onWindowOrderChange(WindowOrderEvent event) { + VWindow[] windows = event.getWindows(); + HashMap orders = new HashMap<>(); + boolean hasEventListener = hasEventListener(EventId.WINDOW_ORDER); + for (VWindow window : windows) { + Connector connector = Util.findConnectorFor(window); + orders.put(window.getWindowOrder(), connector); + if (connector instanceof AbstractConnector + && ((AbstractConnector) connector) + .hasEventListener(EventId.WINDOW_ORDER)) { + hasEventListener = true; + } + } + if (hasEventListener) { + getRpcProxy(WindowOrderRpc.class).windowOrderChanged(orders); + } + } + }; + @Override protected void init() { super.init(); + windowOrderRegistration = VWindow + .addWindowOrderHandler(windowOrderHandler); registerRpc(PageClientRpc.class, new PageClientRpc() { @Override @@ -703,6 +739,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector } } + setWindowOrderAndPosition(); + // Close removed sub windows for (ComponentConnector c : event.getOldChildren()) { if (c.getParent() != this && c instanceof WindowConnector) { @@ -1124,4 +1162,50 @@ public class UIConnector extends AbstractSingleComponentContainerConnector getRpcProxy(UIServerRpc.class).acknowledge(); } + + private void setWindowOrderAndPosition() { + if (windowOrderRegistration != null) { + windowOrderRegistration.removeHandler(); + } + WindowOrderCollector collector = new WindowOrderCollector(); + HandlerRegistration registration = VWindow + .addWindowOrderHandler(collector); + for (ComponentConnector c : getChildComponents()) { + if (c instanceof WindowConnector) { + WindowConnector wc = (WindowConnector) c; + wc.setWindowOrderAndPosition(); + } + } + windowOrderHandler.onWindowOrderChange( + new WindowOrderEvent(collector.getWindows())); + registration.removeHandler(); + windowOrderRegistration = VWindow + .addWindowOrderHandler(windowOrderHandler); + } + + private static class WindowOrderCollector + implements WindowOrderHandler, Comparator { + + private HashSet windows = new HashSet<>(); + + @Override + public void onWindowOrderChange(WindowOrderEvent event) { + windows.addAll(Arrays.asList(event.getWindows())); + } + + @Override + public int compare(VWindow window1, VWindow window2) { + if (window1.getWindowOrder() == window2.getWindowOrder()) { + return 0; + } + return window1.getWindowOrder() > window2.getWindowOrder() ? 1 : -1; + } + + ArrayList getWindows() { + ArrayList result = new ArrayList<>(); + result.addAll(windows); + Collections.sort(result, this); + return result; + } + }; } diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java b/client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java index 3d5d7bbcfd..4e28fb6349 100644 --- a/client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java @@ -38,7 +38,6 @@ import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.LayoutManager; import com.vaadin.client.Paintable; import com.vaadin.client.UIDL; -import com.vaadin.client.communication.RpcProxy; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; import com.vaadin.client.ui.ClickEventHandler; @@ -497,8 +496,7 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector @Override public void onWindowMove(WindowMoveEvent event) { - RpcProxy.create(WindowServerRpc.class, this) - .windowMoved(event.getNewX(), event.getNewY()); - + getRpcProxy(WindowServerRpc.class).windowMoved(event.getNewX(), + event.getNewY()); } } diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java new file mode 100644 index 0000000000..33517462ea --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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.vaadin.client.ui.window; + +import java.util.ArrayList; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.ui.VWindow; + +/** + * Event for window order position updates. + * + * @since 8.0.0 + * + * @author Vaadin Ltd + */ +public class WindowOrderEvent extends GwtEvent { + + private static final Type TYPE = new Type<>(); + + private final ArrayList windows; + + /** + * Creates a new event with the given order. + * + * @param windows + * The new order position for the VWindow + */ + public WindowOrderEvent(ArrayList windows) { + this.windows = windows; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + /** + * Returns windows in order. + * + * @return windows in the specific order + */ + public VWindow[] getWindows() { + return windows.toArray(new VWindow[windows.size()]); + } + + @Override + protected void dispatch(WindowOrderHandler handler) { + handler.onWindowOrderChange(this); + } + + /** + * Gets the type of the event. + * + * @return the type of the event + */ + public static Type getType() { + return TYPE; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java new file mode 100644 index 0000000000..3a492ef27e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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.vaadin.client.ui.window; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for {@link WindowOrderEvent}s. + * + * @since 8.0.0 + * + * @author Vaadin Ltd + */ +public interface WindowOrderHandler extends EventHandler { + + /** + * Called when the VWindow instances changed their order position. + * + * @param event + * Contains windows whose position has changed + */ + public void onWindowOrderChange(WindowOrderEvent event); +} \ No newline at end of file diff --git a/server/src/main/java/com/vaadin/ui/UI.java b/server/src/main/java/com/vaadin/ui/UI.java index 22dcb437f5..dbca873cc1 100644 --- a/server/src/main/java/com/vaadin/ui/UI.java +++ b/server/src/main/java/com/vaadin/ui/UI.java @@ -18,14 +18,18 @@ package com.vaadin.ui; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.lang.reflect.Method; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; @@ -34,6 +38,7 @@ import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.ActionManager; +import com.vaadin.event.ConnectorEventListener; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.MouseEvents.ClickListener; import com.vaadin.event.UIEvents.PollEvent; @@ -62,6 +67,7 @@ import com.vaadin.shared.EventId; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.Registration; import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.WindowOrderRpc; import com.vaadin.shared.ui.ui.DebugWindowClientRpc; import com.vaadin.shared.ui.ui.DebugWindowServerRpc; import com.vaadin.shared.ui.ui.ScrollClientRpc; @@ -70,9 +76,11 @@ import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.shared.ui.ui.UIServerRpc; import com.vaadin.shared.ui.ui.UIState; import com.vaadin.ui.Component.Focusable; +import com.vaadin.ui.Window.WindowOrderChangeListener; import com.vaadin.ui.declarative.Design; import com.vaadin.util.ConnectorHelper; import com.vaadin.util.CurrentInstance; +import com.vaadin.util.ReflectTools; /** * The topmost component in any component hierarchy. There is one UI for every @@ -245,6 +253,21 @@ public abstract class UI extends AbstractSingleComponentContainer } }; + private WindowOrderRpc windowOrderRpc = new WindowOrderRpc() { + + @Override + public void windowOrderChanged( + HashMap windowOrders) { + Map orders = new LinkedHashMap<>(); + for (Entry entry : windowOrders.entrySet()) { + if (entry.getValue() instanceof Window) { + orders.put(entry.getKey(), (Window) entry.getValue()); + } + } + fireWindowOrder(orders); + } + }; + /** * Timestamp keeping track of the last heartbeat of this UI. Updated to the * current time whenever the application receives a heartbeat or UIDL @@ -290,6 +313,7 @@ public abstract class UI extends AbstractSingleComponentContainer public UI(Component content) { registerRpc(rpc); registerRpc(debugRpc); + registerRpc(windowOrderRpc); setSizeFull(); setContent(content); } @@ -387,6 +411,19 @@ public abstract class UI extends AbstractSingleComponentContainer fireEvent(new ClickEvent(this, mouseDetails)); } + /** + * Fire a window order event. + * + * @param windows + * The windows with their orders whose order has been updated. + */ + private void fireWindowOrder(Map windows) { + for (Entry entry : windows.entrySet()) { + entry.getValue().fireWindowOrderChange(entry.getKey()); + } + fireEvent(new WindowOrderUpdateEvent(this, windows.values())); + } + @Override @SuppressWarnings("unchecked") public void changeVariables(Object source, Map variables) { @@ -569,6 +606,7 @@ public abstract class UI extends AbstractSingleComponentContainer markAsDirty(); window.fireClose(); fireComponentDetachEvent(window); + fireWindowOrder(Collections.singletonMap(-1, window)); return true; } @@ -1728,4 +1766,88 @@ public abstract class UI extends AbstractSingleComponentContainer int lastProcessedClientToServerId) { this.lastProcessedClientToServerId = lastProcessedClientToServerId; } + + /** + * Adds a WindowOrderUpdateListener to the UI. + *

+ * The WindowOrderUpdateEvent is fired when the order positions of windows + * are updated. It can happen when some window (this or other) is brought to + * front or detached. + *

+ * The other way to listen window position for specific window is + * {@link Window#addWindowOrderChangeListener(WindowOrderChangeListener)} + * + * @see Window#addWindowOrderChangeListener(WindowOrderChangeListener) + * + * @param listener + * the WindowModeChangeListener to add. + * @since 8.0.0 + * + * @return a registration object for removing the listener + */ + public Registration addWindowOrderUpdateListener( + WindowOrderUpdateListener listener) { + addListener(EventId.WINDOW_ORDER, WindowOrderUpdateEvent.class, + listener, WindowOrderUpdateListener.windowOrderUpdateMethod); + return () -> removeListener(EventId.WINDOW_ORDER, + WindowOrderUpdateEvent.class, listener); + } + + /** + * Event which is fired when the ordering of the windows is updated. + *

+ * The other way to listen window position for specific window is + * {@link Window#addWindowOrderChangeListener(WindowOrderChangeListener)} + * + * @see Window.WindowOrderChangeEvent + * + * @author Vaadin Ltd + * @since 8.0.0 + * + */ + public static class WindowOrderUpdateEvent extends Component.Event { + + private final Collection windows; + + public WindowOrderUpdateEvent(Component source, + Collection windows) { + super(source); + this.windows = windows; + } + + /** + * Gets the windows in the order they appear in the UI: top most window + * is first, bottom one last. + * + * @return the windows collection + */ + public Collection getWindows() { + return windows; + } + } + + /** + * An interface used for listening to Windows order update events. + * + * @since 8.0.0 + * + * @see Window.WindowOrderChangeEvent + */ + public interface WindowOrderUpdateListener extends ConnectorEventListener { + + public static final Method windowOrderUpdateMethod = ReflectTools + .findMethod(WindowOrderUpdateListener.class, + "windowOrderUpdated", WindowOrderUpdateEvent.class); + + /** + * Called when the windows order positions are changed. Use + * {@link WindowOrderUpdateEvent#getWindows()} to get a reference to the + * {@link Window}s whose order positions are updated. Use + * {@link Window#getOrderPosition()} to get window position for specific + * window. + * + * @param event + */ + public void windowOrderUpdated(WindowOrderUpdateEvent event); + } } diff --git a/server/src/main/java/com/vaadin/ui/Window.java b/server/src/main/java/com/vaadin/ui/Window.java index 84e7ca7c7c..028c585863 100644 --- a/server/src/main/java/com/vaadin/ui/Window.java +++ b/server/src/main/java/com/vaadin/ui/Window.java @@ -28,6 +28,7 @@ import java.util.Map; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import com.vaadin.event.ConnectorEventListener; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; @@ -42,6 +43,7 @@ import com.vaadin.event.ShortcutListener; import com.vaadin.server.PaintException; import com.vaadin.server.PaintTarget; import com.vaadin.shared.Connector; +import com.vaadin.shared.EventId; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.window.WindowMode; @@ -109,6 +111,15 @@ public class Window extends Panel */ private List closeShortcuts = new ArrayList<>(4); + /** + * Used to keep the window order position. Order position for unattached + * window is {@code -1}. + *

+ * Window with greatest order position value is on the top and window with 0 + * position value is on the bottom. + */ + private int orderPosition = -1; + /** * Creates a new, empty window */ @@ -317,6 +328,24 @@ public class Window extends Panel return getState(false).positionY; } + /** + * Returns the position of this window in the order of all open windows for + * this UI. + *

+ * Window with position 0 is on the bottom, and window with greatest + * position is at the top. If window has no position (it's not yet attached + * or hidden) then position is {@code -1}. + * + * @see UI#addWindowOrderUpdateListener(com.vaadin.ui.UI.WindowOrderUpdateListener) + * + * @since 8.0.0 + * + * @return window order position. + */ + public int getOrderPosition() { + return orderPosition; + } + /** * Sets the distance of Window top border in pixels from top border of the * containing (main window). Has effect only if in {@link WindowMode#NORMAL} @@ -365,6 +394,96 @@ public class Window extends Panel } } + /** + * Event which is fired when the window order position is changed. + * + * @see UI.WindowOrderUpdateEvent + * + * @author Vaadin Ltd + * + */ + public static class WindowOrderChangeEvent extends Component.Event { + + private final int order; + + public WindowOrderChangeEvent(Component source, int order) { + super(source); + this.order = order; + } + + /** + * Gets the Window. + * + * @return the window + */ + public Window getWindow() { + return (Window) getSource(); + } + + /** + * Gets the new window order position. + * + * @return the new order position + */ + public int getOrder() { + return order; + } + } + + /** + * An interface used for listening to Window order change events. + * + * @see UI.WindowOrderUpdateListener + */ + public interface WindowOrderChangeListener extends ConnectorEventListener { + + public static final Method windowOrderChangeMethod = ReflectTools + .findMethod(WindowOrderChangeListener.class, + "windowOrderChanged", WindowOrderChangeEvent.class); + + /** + * Called when the window order position is changed. Use + * {@link WindowOrderChangeEvent#getWindow()} to get a reference to the + * {@link Window} whose order position is changed. Use + * {@link WindowOrderChangeEvent#getOrder()} to get a new order + * position. + * + * @param event + */ + public void windowOrderChanged(WindowOrderChangeEvent event); + } + + /** + * Adds a WindowOrderChangeListener to the window. + *

+ * The WindowOrderChangeEvent is fired when the order position is changed. + * It can happen when some window (this or other) is brought to front or + * detached. + *

+ * The other way to listen positions of all windows in UI is + * {@link UI#addWindowOrderUpdateListener(com.vaadin.ui.UI.WindowOrderUpdateListener)} + * + * @see UI#addWindowOrderUpdateListener(com.vaadin.ui.UI.WindowOrderUpdateListener) + * + * @param listener + * the WindowModeChangeListener to add. + */ + public Registration addWindowOrderChangeListener( + WindowOrderChangeListener listener) { + addListener(EventId.WINDOW_ORDER, WindowOrderChangeEvent.class, + listener, WindowOrderChangeListener.windowOrderChangeMethod); + return () -> removeListener(EventId.WINDOW_ORDER, + WindowOrderChangeEvent.class, listener); + } + + protected void fireWindowOrderChange(Integer order) { + if (order == null || this.orderPosition != order) { + this.orderPosition = (order == null) ? -1 : order; + fireEvent(new Window.WindowOrderChangeEvent(this, + getOrderPosition())); + } + } + /** * An interface used for listening to Window close events. Add the * CloseListener to a window and diff --git a/shared/src/main/java/com/vaadin/shared/EventId.java b/shared/src/main/java/com/vaadin/shared/EventId.java index c3a785a988..78580abc41 100644 --- a/shared/src/main/java/com/vaadin/shared/EventId.java +++ b/shared/src/main/java/com/vaadin/shared/EventId.java @@ -25,4 +25,5 @@ public interface EventId extends Serializable { public static final String POLL = "poll"; public static final String CHANGE = "change"; public static final String CONTEXT_CLICK = "cClick"; + public static final String WINDOW_ORDER = "windowOrder"; } diff --git a/shared/src/main/java/com/vaadin/shared/ui/WindowOrderRpc.java b/shared/src/main/java/com/vaadin/shared/ui/WindowOrderRpc.java new file mode 100644 index 0000000000..e3cd8290a7 --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/WindowOrderRpc.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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.vaadin.shared.ui; + +import java.util.HashMap; + +import com.vaadin.shared.Connector; +import com.vaadin.shared.communication.ServerRpc; + +/** + * Window order RPC interface. + *

+ * Notifies server when windows order is changed. + * + * @author Vaadin Ltd + * @since 8.0.0 + * + */ +public interface WindowOrderRpc extends ServerRpc { + + /** + * Sends RPC request about windows order change. + * + * @param windowOrders + * new windows order + */ + void windowOrderChanged(HashMap windowOrders); +} \ No newline at end of file diff --git a/uitest/src/main/java/com/vaadin/tests/components/window/WindowOrder.java b/uitest/src/main/java/com/vaadin/tests/components/window/WindowOrder.java new file mode 100644 index 0000000000..d3a17006ba --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/window/WindowOrder.java @@ -0,0 +1,175 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.components.window; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Label; +import com.vaadin.ui.Window; +import com.vaadin.ui.Window.WindowOrderChangeEvent; +import com.vaadin.ui.Window.WindowOrderChangeListener; + +/** + * Test UI for accessing to window order position. + * + * @author Vaadin Ltd + */ +public class WindowOrder extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + w1 = new Window(); + w1.setCaption("Window1"); + w1.addStyleName("window1"); + + w2 = new Window(); + w2.setCaption("Window2"); + w2.addStyleName("window2"); + + w3 = new Window(); + w3.setCaption("Window3"); + w3.addStyleName("window3"); + + getUI().addWindow(w1); + getUI().addWindow(w2); + getUI().addWindow(w3); + OrderListener listener = new OrderListener(); + for (Window window : getUI().getWindows()) { + window.addWindowOrderChangeListener(listener); + } + + w4 = new Window(); + w4.setCaption("Window4"); + w4.addStyleName("window4"); + w4.addWindowOrderChangeListener(listener); + + infoLabel = createLabel("info-label"); + uiLabel = createLabel("ui-label"); + + getUI().addWindowOrderUpdateListener(new WindowOrderListener()); + + addComponent(infoLabel); + addComponent(uiLabel); + + Button first = new Button("Bring first to front", new ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + w1.bringToFront(); + } + }); + first.addStyleName("bring-to-front-first"); + addComponent(first); + getLayout().setComponentAlignment(first, Alignment.MIDDLE_RIGHT); + + Button all = new Button("Bring to front all windows", + new ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + w3.bringToFront(); + w1.bringToFront(); + w2.bringToFront(); + } + }); + all.addStyleName("bring-to-front-all"); + addComponent(all); + getLayout().setComponentAlignment(all, Alignment.MIDDLE_RIGHT); + + Button detach = new Button("Detach last window", new ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + getUI().removeWindow(w3); + } + }); + detach.addStyleName("detach-window"); + addComponent(detach); + getLayout().setComponentAlignment(detach, Alignment.MIDDLE_RIGHT); + + Button add = new Button("Add new window", new ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + getUI().addWindow(w4); + } + }); + add.addStyleName("add-window"); + addComponent(add); + getLayout().setComponentAlignment(add, Alignment.MIDDLE_RIGHT); + } + + @Override + protected String getTestDescription() { + return "Window order position access and listeners for order change events."; + } + + @Override + protected Integer getTicketNumber() { + return 14325; + } + + private Label createLabel(String style) { + Label label = new Label(); + label.addStyleName(style); + return label; + } + + private class OrderListener implements WindowOrderChangeListener { + + @Override + public void windowOrderChanged(WindowOrderChangeEvent event) { + infoLabel.removeStyleName("w4--1"); + infoLabel.addStyleName("w4-" + w4.getOrderPosition()); + + if (event.getWindow() == w3 && event.getOrder() == -1) { + Label detached = new Label("Window 3 is detached"); + detached.addStyleName("w3-detached"); + detached.addStyleName("w3-" + w3.getOrderPosition()); + addComponent(detached); + } + + Window window = event.getWindow(); + Label label = new Label(String.valueOf(window.getOrderPosition())); + label.addStyleName("event-order" + event.getOrder()); + window.setContent(label); + } + } + + private class WindowOrderListener implements WindowOrderUpdateListener { + + @Override + public void windowOrderUpdated(WindowOrderUpdateEvent event) { + uiLabel.removeStyleName(infoLabel.getStyleName()); + for (Window window : event.getWindows()) { + uiLabel.addStyleName( + window.getStyleName() + "-" + window.getOrderPosition()); + } + } + } + + private Window w1; + private Window w2; + private Window w3; + private Window w4; + private Label infoLabel; + + private Label uiLabel; +} \ No newline at end of file diff --git a/uitest/src/test/java/com/vaadin/tests/components/window/WindowOrderTest.java b/uitest/src/test/java/com/vaadin/tests/components/window/WindowOrderTest.java new file mode 100644 index 0000000000..c3faea3d39 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/window/WindowOrderTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.components.window; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.tests.tb3.MultiBrowserTest; + +/** + * Test for window order position access. + * + * @author Vaadin Ltd + */ +public class WindowOrderTest extends MultiBrowserTest { + + @Test + public void orderGetterTest() { + openTestURL(); + + checkPositionsAfterFirstWindowActivation(); + + checkPositionsAfterActivationThirdFirstSecond(); + + checkPositionsAfterDetachingThirdWindow(); + + checkPositionsAfterNewWindowAttach(); + } + + private void checkPositionsAfterFirstWindowActivation() { + // Bring the first window to front and check order positions of the + // windows + findElement(By.className("bring-to-front-first")).click(); + Assert.assertTrue( + "The first window has wrong order position after bring first to front", + hasOrder("window1", 2)); + Assert.assertTrue( + "The first window position is incorrectly updated via UI listener after bring first to front", + hasOrderInUi("window1", 2)); + Assert.assertTrue( + "The second window has wrong order position after bring first to front", + hasOrder("window2", 0)); + Assert.assertTrue( + "The second window position is incorrectly updated via UI after bring first to front", + hasOrderInUi("window2", 0)); + Assert.assertTrue( + "The third window has wrong order position after bring first to front", + hasOrder("window3", 1)); + Assert.assertTrue( + "The third window position is incorrectly updated via UI after bring first to front", + hasOrderInUi("window3", 1)); + Assert.assertTrue( + "Last window is not attached and should have '-1' position, but hasn't.", + lastWindowHasOrder(-1)); + } + + private void checkPositionsAfterActivationThirdFirstSecond() { + // Bring third, first and second window at once (exactly in this order) + // to front and check order positions of the + // windows + findElement(By.className("bring-to-front-all")).click(); + + Assert.assertTrue( + "The first window has wrong order position after bring all to front", + hasOrder("window2", 2)); + Assert.assertTrue( + "The first window position is incorrectly updated via UI after bring all to front", + hasOrderInUi("window2", 2)); + Assert.assertTrue( + "The second window has wrong order position after bring all to front", + hasOrder("window1", 1)); + Assert.assertTrue( + "The second window position is incorrectly updated via UI after bring all to front", + hasOrderInUi("window1", 1)); + Assert.assertTrue( + "The third window has wrong order position after bring all to front", + hasOrder("window3", 0)); + Assert.assertTrue( + "The third window position is incorrectly updated via UI after bring all to front", + hasOrderInUi("window3", 0)); + Assert.assertTrue( + "Last window is not attached and should have '-1' position, but hasn't.", + lastWindowHasOrder(-1)); + } + + private void checkPositionsAfterDetachingThirdWindow() { + // Detach third window and check order positions of the + // windows + findElement(By.className("detach-window")).click(); + + Assert.assertTrue( + "The first window has wrong order position after detach last window", + hasOrder("window2", 1)); + Assert.assertTrue( + "The first window position is incorrectly updated after detach last window", + hasOrderInUi("window2", 1)); + Assert.assertTrue( + "The second window has wrong order position after detach last window", + hasOrder("window1", 0)); + Assert.assertTrue( + "The second window position is incorrectly updated after detach last window", + hasOrderInUi("window1", 0)); + WebElement thirdWindowInfo = findElement(By.className("w3-detached")); + Assert.assertTrue("The third window has wrong order after detach", + thirdWindowInfo.getAttribute("class").contains("w3--1")); + Assert.assertTrue( + "The third window position is incorrectly updated after detach last window", + hasOrderInUi("window3", -1)); + Assert.assertTrue( + "Last window is not attached and should have '-1' position, but hasn't.", + lastWindowHasOrder(-1)); + } + + private void checkPositionsAfterNewWindowAttach() { + // Attach new window and check order positions of the + // windows + findElement(By.className("add-window")).click(); + + Assert.assertTrue( + "The first window has wrong order position after add new window", + hasOrder("window2", 1)); + Assert.assertTrue( + "The first window position is incorrectly updated after add new window", + hasOrderInUi("window2", 1)); + Assert.assertTrue( + "The second window has wrong order position after add new window", + hasOrder("window1", 0)); + Assert.assertTrue( + "The second window position is incorrectly updated after add new window", + hasOrderInUi("window1", 0)); + Assert.assertTrue( + "The last window has wrong order position after add new window", + hasOrder("window4", 2)); + Assert.assertTrue( + "The last window position is incorrectly updated after add new window", + hasOrderInUi("window4", 2)); + } + + private WebElement findElement(String styleName) { + return findElement(By.className(styleName)); + } + + private boolean hasOrder(String window, int order) { + WebElement win = findElement(window); + WebElement content = win.findElement(By.className("v-label")); + return content.getText().equals(String.valueOf(order)) && content + .getAttribute("class").contains("event-order" + order); + } + + private boolean hasOrderInUi(String window, int order) { + WebElement uiLabel = findElement(By.className("ui-label")); + return uiLabel.getAttribute("class").contains(window + '-' + order); + } + + private boolean lastWindowHasOrder(int order) { + WebElement info = findElement("info-label"); + String clazz = info.getAttribute("class"); + String style = "w4-" + order; + boolean hasOrder = clazz.contains(style); + if (!hasOrder) { + return false; + } + clazz = clazz.replace(style, ""); + return !clazz.contains("w4"); + } + +} -- 2.39.5