/* @ITMillApache2LicenseForJavaFiles@ */ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import com.vaadin.Application; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.event.FieldEvents.FocusNotifier; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutAction.KeyCode; import com.vaadin.event.ShortcutAction.ModifierKey; import com.vaadin.event.ShortcutListener; import com.vaadin.terminal.DownloadStream; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.ParameterHandler; import com.vaadin.terminal.Resource; import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.Terminal; import com.vaadin.terminal.URIHandler; import com.vaadin.terminal.gwt.client.ui.VWindow; /** * A component that represents an application (browser native) window or a sub * window. *

* If the window is a application window or a sub window depends on how it is * added to the application. Adding a {@code Window} to a {@code Window} using * {@link Window#addWindow(Window)} makes it a sub window and adding a * {@code Window} to the {@code Application} using * {@link Application#addWindow(Window)} makes it an application window. *

*

* An application window is the base of any view in a Vaadin application. All * applications contain a main application window (set using * {@link Application#setMainWindow(Window)} which is what is initially shown to * the user. The contents of a window is set using * {@link #setContent(ComponentContainer)}. The contents can in turn contain * other components. For multi-tab applications there is one window instance per * opened tab. *

*

* A sub window is floating popup style window that can be added to an * application window. Like the application window its content is set using * {@link #setContent(ComponentContainer)}. A sub window can be positioned on * the screen using absolute coordinates (pixels). The default content of the * Window is set to be suitable for application windows. For sub windows it * might be necessary to set the size of the content to work as expected. *

*

* Window caption is displayed in the browser title bar for application level * windows and in the window header for sub windows. *

*

* Certain methods in this class are only meaningful for sub windows and other * parts only for application windows. These are marked using Sub window * only and Application window only respectively in the javadoc. *

*

* Sub window is to be split into a separate component in Vaadin 7. *

* * @author IT Mill Ltd. * @version * @VERSION@ * @since 3.0 */ @SuppressWarnings("serial") @ClientWidget(VWindow.class) public class Window extends Panel implements URIHandler, ParameterHandler, FocusNotifier, BlurNotifier { /** * Application window only. A border style used for opening resources * in a window without a border. */ public static final int BORDER_NONE = 0; /** * Application window only. A border style used for opening resources * in a window with a minimal border. */ public static final int BORDER_MINIMAL = 1; /** * Application window only. A border style that indicates that the * default border style should be used when opening resources. */ public static final int BORDER_DEFAULT = 2; /** * Application window only. The user terminal for this window. */ private Terminal terminal = null; /** * Application window only. The application this window is attached * to or null. */ private Application application = null; /** * Application window only. List of URI handlers for this window. */ private LinkedList uriHandlerList = null; /** * Application window only. List of parameter handlers for this * window. */ private LinkedList parameterHandlerList = null; /** * Application window only. List of sub windows in this window. A sub * window cannot have other sub windows. */ private final LinkedHashSet subwindows = new LinkedHashSet(); /** * Application window only. Explicitly specified theme of this window * or null if the application theme should be used. */ private String theme = null; /** * Application window only. Resources to be opened automatically on * next repaint. The list is automatically cleared when it has been sent to * the client. */ private final LinkedList openList = new LinkedList(); /** * Application window only. Unique name of the window used to * identify it. */ private String name = null; /** * Application window only. Border mode of the Window. */ private int border = BORDER_DEFAULT; /** * Sub window only. Top offset in pixels for the sub window (relative * to the parent application window) or -1 if unspecified. */ private int positionY = -1; /** * Sub window only. Left offset in pixels for the sub window * (relative to the parent application window) or -1 if unspecified. */ private int positionX = -1; /** * Application window only. A list of notifications that are waiting * to be sent to the client. Cleared (set to null) when the notifications * have been sent. */ private LinkedList notifications; /** * Sub window only. Modality flag for sub window. */ private boolean modal = false; /** * Sub window only. Controls if the end user can resize the window. */ private boolean resizable = true; /** * Sub window only. Controls if the end user can move the window by * dragging. */ private boolean draggable = true; /** * Sub window only. Flag which is true if the window is centered on * the screen. */ private boolean centerRequested = false; /** * Component that should be focused after the next repaint. Null if no focus * change should take place. */ private Focusable pendingFocus; /** * Application window only. A list of javascript commands that are * waiting to be sent to the client. Cleared (set to null) when the commands * have been sent. */ private ArrayList jsExecQueue = null; /** * The component that should be scrolled into view after the next repaint. * Null if nothing should be scrolled into view. */ private Component scrollIntoView; /** * Creates a new unnamed window with a default layout. */ public Window() { this("", null); } /** * Creates a new unnamed window with a default layout and given title. * * @param caption * the title of the window. */ public Window(String caption) { this(caption, null); } /** * Creates a new unnamed window with the given content and title. * * @param caption * the title of the window. * @param content * the contents of the window */ public Window(String caption, ComponentContainer content) { super(caption, content); setScrollable(true); setSizeUndefined(); } /* * (non-Javadoc) * * @see com.vaadin.ui.Panel#addComponent(com.vaadin.ui.Component) */ @Override public void addComponent(Component c) { if (c instanceof Window) { throw new IllegalArgumentException( "Window cannot be added to another via addComponent. " + "Use addWindow(Window) instead."); } super.addComponent(c); } /** * Application window only. Gets the user terminal. * * @return the user terminal */ public Terminal getTerminal() { return terminal; } /* ********************************************************************* */ /** * Gets the parent window of the component. *

* This is always the window itself. *

* * @see Component#getWindow() * @return the window itself */ @Override public final Window getWindow() { return this; } /* * (non-Javadoc) * * @see com.vaadin.ui.AbstractComponent#getApplication() */ @Override public final Application getApplication() { if (getParent() == null) { return application; } return (getParent()).getApplication(); } /** * Gets the parent component of the window. * *

* The parent of an application window is always null. The parent of a sub * window is the application window the sub window is attached to. *

* * @return the parent window * @see Component#getParent() */ @Override public final Window getParent() { return (Window) super.getParent(); } /* ********************************************************************* */ /** * Application window only. Adds a new URI handler to this window. If * this is a sub window the URI handler is attached to the parent * application window. * * @param handler * the URI handler to add. */ public void addURIHandler(URIHandler handler) { if (getParent() != null) { // this is subwindow, attach to main level instead // TODO hold internal list also and remove on detach Window mainWindow = getParent(); mainWindow.addURIHandler(handler); } else { if (uriHandlerList == null) { uriHandlerList = new LinkedList(); } synchronized (uriHandlerList) { if (!uriHandlerList.contains(handler)) { uriHandlerList.addLast(handler); } } } } /** * Application window only. Removes the URI handler from this window. * If this is a sub window the URI handler is removed from the parent * application window. * * @param handler * the URI handler to remove. */ public void removeURIHandler(URIHandler handler) { if (getParent() != null) { // this is subwindow Window mainWindow = getParent(); mainWindow.removeURIHandler(handler); } else { if (handler == null || uriHandlerList == null) { return; } synchronized (uriHandlerList) { uriHandlerList.remove(handler); if (uriHandlerList.isEmpty()) { uriHandlerList = null; } } } } /** * Application window only. Handles an URI by passing the URI to all * URI handlers defined using {@link #addURIHandler(URIHandler)}. All URI * handlers are called for each URI but no more than one handler may return * a {@link DownloadStream}. If more than one stream is returned a * {@code RuntimeException} is thrown. * * @param context * The URL of the application * @param relativeUri * The URI relative to {@code context} * @return A {@code DownloadStream} that one of the URI handlers returned, * null if no {@code DownloadStream} was returned. */ public DownloadStream handleURI(URL context, String relativeUri) { DownloadStream result = null; if (uriHandlerList != null) { Object[] handlers; synchronized (uriHandlerList) { handlers = uriHandlerList.toArray(); } for (int i = 0; i < handlers.length; i++) { final DownloadStream ds = ((URIHandler) handlers[i]).handleURI( context, relativeUri); if (ds != null) { if (result != null) { throw new RuntimeException("handleURI for " + context + " uri: '" + relativeUri + "' returns ambigious result."); } result = ds; } } } return result; } /* ********************************************************************* */ /** * Application window only. Adds a new parameter handler to this * window. If this is a sub window the parameter handler is attached to the * parent application window. * * @param handler * the parameter handler to add. */ public void addParameterHandler(ParameterHandler handler) { if (getParent() != null) { // this is subwindow // TODO hold internal list also and remove on detach Window mainWindow = getParent(); mainWindow.addParameterHandler(handler); } else { if (parameterHandlerList == null) { parameterHandlerList = new LinkedList(); } synchronized (parameterHandlerList) { if (!parameterHandlerList.contains(handler)) { parameterHandlerList.addLast(handler); } } } } /** * Application window only. Removes the parameter handler from this * window. If this is a sub window the parameter handler is removed from the * parent application window. * * @param handler * the parameter handler to remove. */ public void removeParameterHandler(ParameterHandler handler) { if (getParent() != null) { // this is subwindow Window mainWindow = getParent(); mainWindow.removeParameterHandler(handler); } else { if (handler == null || parameterHandlerList == null) { return; } synchronized (parameterHandlerList) { parameterHandlerList.remove(handler); if (parameterHandlerList.isEmpty()) { parameterHandlerList = null; } } } } /** * Application window only. Handles parameters by passing the * parameters to all {@code ParameterHandler}s defined using * {@link #addParameterHandler(ParameterHandler)}. All * {@code ParameterHandler}s are called for each set of parameters. * * @param parameters * a map containing the parameter names and values * @see ParameterHandler#handleParameters(Map) */ public void handleParameters(Map parameters) { if (parameterHandlerList != null) { Object[] handlers; synchronized (parameterHandlerList) { handlers = parameterHandlerList.toArray(); } for (int i = 0; i < handlers.length; i++) { ((ParameterHandler) handlers[i]).handleParameters(parameters); } } } /* ********************************************************************* */ /** * Application window only. Gets the theme for this window. *

* If the theme for this window is not explicitly set, the application theme * name is returned. If the window is not attached to an application, the * terminal default theme name is returned. If the theme name cannot be * determined, null is returned *

*

* Subwindows do not support themes and return the theme used by the parent * window *

* * @return the name of the theme used for the window */ public String getTheme() { if (getParent() != null) { return (getParent()).getTheme(); } if (theme != null) { return theme; } if ((application != null) && (application.getTheme() != null)) { return application.getTheme(); } if (terminal != null) { return terminal.getDefaultTheme(); } return null; } /** * Application window only. Sets the name of the theme to use for * this window. Changing the theme will cause the page to be reloaded. * * @param theme * the name of the new theme for this window or null to use the * application theme. */ public void setTheme(String theme) { if (getParent() != null) { throw new UnsupportedOperationException( "Setting theme for sub-windows is not supported."); } this.theme = theme; requestRepaint(); } /* * (non-Javadoc) * * @see com.vaadin.ui.Panel#paintContent(com.vaadin.terminal.PaintTarget) */ @Override public synchronized void paintContent(PaintTarget target) throws PaintException { // Sets the window name final String name = getName(); target.addAttribute("name", name == null ? "" : name); // Sets the window theme final String theme = getTheme(); target.addAttribute("theme", theme == null ? "" : theme); if (modal) { target.addAttribute("modal", true); } if (resizable) { target.addAttribute("resizable", true); } if (!draggable) { // Inverted to prevent an extra attribute for almost all sub windows target.addAttribute("fixedposition", true); } if (bringToFront != null) { target.addAttribute("bringToFront", bringToFront.intValue()); bringToFront = null; } if (centerRequested) { target.addAttribute("center", true); centerRequested = false; } if (scrollIntoView != null) { target.addAttribute("scrollTo", scrollIntoView); scrollIntoView = null; } // Marks the main window if (getApplication() != null && this == getApplication().getMainWindow()) { target.addAttribute("main", true); } if (getContent() != null) { if (getContent().getHeightUnits() == Sizeable.UNITS_PERCENTAGE) { target.addAttribute("layoutRelativeHeight", true); } if (getContent().getWidthUnits() == Sizeable.UNITS_PERCENTAGE) { target.addAttribute("layoutRelativeWidth", true); } } // Open requested resource synchronized (openList) { if (!openList.isEmpty()) { for (final Iterator i = openList.iterator(); i .hasNext();) { (i.next()).paintContent(target); } openList.clear(); } } // Contents of the window panel is painted super.paintContent(target); // Add executable javascripts if needed if (jsExecQueue != null) { for (String script : jsExecQueue) { target.startTag("execJS"); target.addAttribute("script", script); target.endTag("execJS"); } jsExecQueue = null; } // Window position target.addVariable(this, "positionx", getPositionX()); target.addVariable(this, "positiony", getPositionY()); // Window closing target.addVariable(this, "close", false); if (getParent() == null) { // Paint subwindows for (final Iterator i = subwindows.iterator(); i.hasNext();) { final Window w = i.next(); w.paint(target); } } else { // mark subwindows target.addAttribute("sub", true); } // Paint notifications if (notifications != null) { target.startTag("notifications"); for (final Iterator it = notifications.iterator(); it .hasNext();) { final Notification n = it.next(); target.startTag("notification"); if (n.getCaption() != null) { target.addAttribute("caption", n.getCaption()); } if (n.getMessage() != null) { target.addAttribute("message", n.getMessage()); } if (n.getIcon() != null) { target.addAttribute("icon", n.getIcon()); } target.addAttribute("position", n.getPosition()); target.addAttribute("delay", n.getDelayMsec()); if (n.getStyleName() != null) { target.addAttribute("style", n.getStyleName()); } target.endTag("notification"); } target.endTag("notifications"); notifications = null; } if (pendingFocus != null) { // ensure focused component is still attached to this main window if (pendingFocus.getWindow() == this || (pendingFocus.getWindow() != null && pendingFocus .getWindow().getParent() == this)) { target.addAttribute("focused", pendingFocus); } pendingFocus = null; } } /* ********************************************************************* */ /** * Scrolls any component between the component and window to a suitable * position so the component is visible to the user. The given component * must be inside this window. * * @param component * the component to be scrolled into view * @throws IllegalArgumentException * if {@code component} is not inside this window */ public void scrollIntoView(Component component) throws IllegalArgumentException { if (component.getWindow() != this) { throw new IllegalArgumentException( "The component where to scroll must be inside this window."); } scrollIntoView = component; requestRepaint(); } /** * Opens the given resource in this window. The contents of this Window is * replaced by the {@code Resource}. * * @param resource * the resource to show in this window */ public void open(Resource resource) { synchronized (openList) { if (!openList.contains(resource)) { openList.add(new OpenResource(resource, null, -1, -1, BORDER_DEFAULT)); } } requestRepaint(); } /* ********************************************************************* */ /** * Opens the given resource in a window with the given name. *

* The supplied {@code windowName} is used as the target name in a * window.open call in the client. This means that special values such as * "_blank", "_self", "_top", "_parent" have special meaning. An empty or * null window name is also a special case. *

*

* "", null and "_self" as {@code windowName} all causes the resource to be * opened in the current window, replacing any old contents. For * downloadable content you should avoid "_self" as "_self" causes the * client to skip rendering of any other changes as it considers them * irrelevant (the page will be replaced by the resource). This can speed up * the opening of a resource, but it might also put the client side into an * inconsistent state if the window content is not completely replaced e.g., * if the resource is downloaded instead of displayed in the browser. *

*

* "_blank" as {@code windowName} causes the resource to always be opened in * a new window or tab (depends on the browser and browser settings). *

*

* "_top" and "_parent" as {@code windowName} works as specified by the HTML * standard. *

*

* Any other {@code windowName} will open the resource in a window with that * name, either by opening a new window/tab in the browser or by replacing * the contents of an existing window with that name. *

* * @param resource * the resource. * @param windowName * the name of the window. */ public void open(Resource resource, String windowName) { synchronized (openList) { if (!openList.contains(resource)) { openList.add(new OpenResource(resource, windowName, -1, -1, BORDER_DEFAULT)); } } requestRepaint(); } /** * Opens the given resource in a window with the given size, border and * name. For more information on the meaning of {@code windowName}, see * {@link #open(Resource, String)}. * * @param resource * the resource. * @param windowName * the name of the window. * @param width * the width of the window in pixels * @param height * the height of the window in pixels * @param border * the border style of the window. See {@link #BORDER_NONE * Window.BORDER_* constants} */ public void open(Resource resource, String windowName, int width, int height, int border) { synchronized (openList) { if (!openList.contains(resource)) { openList.add(new OpenResource(resource, windowName, width, height, border)); } } requestRepaint(); } /* ********************************************************************* */ /** * Gets the full URL of the window. The returned URL is window specific and * can be used to directly refer to the window. *

* Note! This method can not be used for portlets. *

* * @return the URL of the window or null if the window is not attached to an * application */ public URL getURL() { if (application == null) { return null; } try { return new URL(application.getURL(), getName() + "/"); } catch (final MalformedURLException e) { throw new RuntimeException( "Internal problem getting window URL, please report"); } } /** * Application window only. Gets the unique name of the window. The * name of the window is used to uniquely identify it. *

* The name also determines the URL that can be used for direct access to a * window. All windows can be accessed through * {@code http://host:port/app/win} where {@code http://host:port/app} is * the application URL (as returned by {@link Application#getURL()} and * {@code win} is the window name. *

*

* Note! Portlets do not support direct window access through URLs. *

* * @return the Name of the Window. */ public String getName() { return name; } /** * Returns the border style of the window. * * @see #setBorder(int) * @return the border style for the window */ public int getBorder() { return border; } /** * Sets the border style for this window. Valid values are * {@link Window#BORDER_NONE}, {@link Window#BORDER_MINIMAL}, * {@link Window#BORDER_DEFAULT}. *

* Note! Setting this seems to currently have no effect whatsoever on * the window. *

* * @param border * the border style to set */ public void setBorder(int border) { this.border = border; } /** * Sets the application this window is attached to. * *

* This method is called by the framework and should not be called directly * from application code. {@link com.vaadin.Application#addWindow(Window)} * should be used to add the window to an application and * {@link com.vaadin.Application#removeWindow(Window)} to remove the window * from the application. *

*

* This method invokes {@link Component#attach()} and * {@link Component#detach()} methods when necessary. *

* * @param application * the application the window is attached to */ public void setApplication(Application application) { // If the application is not changed, dont do nothing if (application == this.application) { return; } // Sends detach event if the window is connected to application if (this.application != null) { detach(); } // Connects to new parent this.application = application; // Sends the attach event if connected to a window if (application != null) { attach(); } } /** * Application window only. Sets the unique name of the window. The * name of the window is used to uniquely identify it inside the * application. *

* The name also determines the URL that can be used for direct access to a * window. All windows can be accessed through * {@code http://host:port/app/win} where {@code http://host:port/app} is * the application URL (as returned by {@link Application#getURL()} and * {@code win} is the window name. *

*

* This method can only be called before the window is added to an * application. *

*

* Note! Portlets do not support direct window access through URLs. *

* * @param name * the new name for the window or null if the application should * automatically assign a name to it * @throws IllegalStateException * if the window is attached to an application */ public void setName(String name) throws IllegalStateException { // The name can not be changed in application if (getApplication() != null) { throw new IllegalStateException( "Window name can not be changed while " + "the window is in application"); } this.name = name; } /** * Sets the user terminal. Used by the terminal adapter, should never be * called from application code. * * @param type * the terminal to set. */ public void setTerminal(Terminal type) { terminal = type; } /** * Private class for storing properties related to opening resources. */ private class OpenResource implements Serializable { /** * The resource to open */ private final Resource resource; /** * The name of the target window */ private final String name; /** * The width of the target window */ private final int width; /** * The height of the target window */ private final int height; /** * The border style of the target window */ private final int border; /** * Creates a new open resource. * * @param resource * The resource to open * @param name * The name of the target window * @param width * The width of the target window * @param height * The height of the target window * @param border * The border style of the target window */ private OpenResource(Resource resource, String name, int width, int height, int border) { this.resource = resource; this.name = name; this.width = width; this.height = height; this.border = border; } /** * Paints the open request. Should be painted inside the window. * * @param target * the paint target * @throws PaintException * if the paint operation fails */ private void paintContent(PaintTarget target) throws PaintException { target.startTag("open"); target.addAttribute("src", resource); if (name != null && name.length() > 0) { target.addAttribute("name", name); } if (width >= 0) { target.addAttribute("width", width); } if (height >= 0) { target.addAttribute("height", height); } switch (border) { case Window.BORDER_MINIMAL: target.addAttribute("border", "minimal"); break; case Window.BORDER_NONE: target.addAttribute("border", "none"); break; } target.endTag("open"); } } /* * (non-Javadoc) * * @see com.vaadin.ui.Panel#changeVariables(java.lang.Object, java.util.Map) */ @Override public void changeVariables(Object source, Map variables) { boolean sizeHasChanged = false; // size is handled in super class, but resize events only in windows -> // so detect if size change occurs before super.changeVariables() if (variables.containsKey("height") && (getHeightUnits() != UNITS_PIXELS || (Integer) variables .get("height") != getHeight())) { sizeHasChanged = true; } if (variables.containsKey("width") && (getWidthUnits() != UNITS_PIXELS || (Integer) variables .get("width") != getWidth())) { sizeHasChanged = true; } super.changeVariables(source, variables); // Positioning final Integer positionx = (Integer) variables.get("positionx"); if (positionx != null) { final int x = positionx.intValue(); // This is information from the client so it is already using the // position. No need to repaint. setPositionX(x < 0 ? -1 : x, false); } final Integer positiony = (Integer) variables.get("positiony"); if (positiony != null) { final int y = positiony.intValue(); // This is information from the client so it is already using the // position. No need to repaint. setPositionY(y < 0 ? -1 : y, false); } if (isClosable()) { // Closing final Boolean close = (Boolean) variables.get("close"); if (close != null && close.booleanValue()) { close(); } } // fire event if size has really changed if (sizeHasChanged) { fireResize(); } if (variables.containsKey(FocusEvent.EVENT_ID)) { fireEvent(new FocusEvent(this)); } else if (variables.containsKey(BlurEvent.EVENT_ID)) { fireEvent(new BlurEvent(this)); } } /** * Method that handles window closing (from UI). * *

* By default, sub-windows are removed from their respective parent windows * and thus visually closed on browser-side. Browser-level windows also * closed on the client-side, but they are not implicitly removed from the * application. *

* *

* To explicitly close a sub-window, use {@link #removeWindow(Window)}. To * react to a window being closed (after it is closed), register a * {@link CloseListener}. *

*/ protected void close() { Window parent = getParent(); if (parent == null) { fireClose(); } else { // subwindow is removed from parent parent.removeWindow(this); } } /** * Gets the distance of Window left border in pixels from left border of the * containing (main window). * * @return the Distance of Window left border in pixels from left border of * the containing (main window). or -1 if unspecified. * @since 4.0.0 */ public int getPositionX() { return positionX; } /** * Sets the distance of Window left border in pixels from left border of the * containing (main window). * * @param positionX * the Distance of Window left border in pixels from left border * of the containing (main window). or -1 if unspecified. * @since 4.0.0 */ public void setPositionX(int positionX) { setPositionX(positionX, true); } /** * Sets the distance of Window left border in pixels from left border of the * containing (main window). * * @param positionX * the Distance of Window left border in pixels from left border * of the containing (main window). or -1 if unspecified. * @param repaintRequired * true if the window needs to be repainted, false otherwise * @since 6.3.4 */ private void setPositionX(int positionX, boolean repaintRequired) { this.positionX = positionX; centerRequested = false; if (repaintRequired) { requestRepaint(); } } /** * Gets the distance of Window top border in pixels from top border of the * containing (main window). * * @return Distance of Window top border in pixels from top border of the * containing (main window). or -1 if unspecified . * * @since 4.0.0 */ public int getPositionY() { return positionY; } /** * Sets the distance of Window top border in pixels from top border of the * containing (main window). * * @param positionY * the Distance of Window top border in pixels from top border of * the containing (main window). or -1 if unspecified * * @since 4.0.0 */ public void setPositionY(int positionY) { setPositionY(positionY, true); } /** * Sets the distance of Window top border in pixels from top border of the * containing (main window). * * @param positionY * the Distance of Window top border in pixels from top border of * the containing (main window). or -1 if unspecified * @param repaintRequired * true if the window needs to be repainted, false otherwise * * @since 6.3.4 */ private void setPositionY(int positionY, boolean repaintRequired) { this.positionY = positionY; centerRequested = false; if (repaintRequired) { requestRepaint(); } } private static final Method WINDOW_CLOSE_METHOD; static { try { WINDOW_CLOSE_METHOD = CloseListener.class.getDeclaredMethod( "windowClose", new Class[] { CloseEvent.class }); } catch (final java.lang.NoSuchMethodException e) { // This should never happen throw new java.lang.RuntimeException( "Internal error, window close method not found"); } } public class CloseEvent extends Component.Event { /** * * @param source */ public CloseEvent(Component source) { super(source); } /** * Gets the Window. * * @return the window. */ public Window getWindow() { return (Window) getSource(); } } /** * An interface used for listening to Window close events. Add the * CloseListener to a browser level window or a sub window and * {@link CloseListener#windowClose(CloseEvent)} will be called whenever the * user closes the window. * *

* Since Vaadin 6.5, removing windows using {@link #removeWindow(Window)} * does fire the CloseListener. *

*/ public interface CloseListener extends Serializable { /** * Called when the user closes a window. Use * {@link CloseEvent#getWindow()} to get a reference to the * {@link Window} that was closed. * * @param e * Event containing */ public void windowClose(CloseEvent e); } /** * Adds a CloseListener to the window. * * For a sub window the CloseListener is fired when the user closes it * (clicks on the close button). * * For a browser level window the CloseListener is fired when the browser * level window is closed. Note that closing a browser level window does not * mean it will be destroyed. * *

* Since Vaadin 6.5, removing windows using {@link #removeWindow(Window)} * does fire the CloseListener. *

* * @param listener * the CloseListener to add. */ public void addListener(CloseListener listener) { addListener(CloseEvent.class, listener, WINDOW_CLOSE_METHOD); } /** * Removes the CloseListener from the window. * *

* For more information on CloseListeners see {@link CloseListener}. *

* * @param listener * the CloseListener to remove. */ public void removeListener(CloseListener listener) { removeListener(CloseEvent.class, listener, WINDOW_CLOSE_METHOD); } protected void fireClose() { fireEvent(new Window.CloseEvent(this)); } /** * Method for the resize event. */ private static final Method WINDOW_RESIZE_METHOD; static { try { WINDOW_RESIZE_METHOD = ResizeListener.class.getDeclaredMethod( "windowResized", new Class[] { ResizeEvent.class }); } catch (final java.lang.NoSuchMethodException e) { // This should never happen throw new java.lang.RuntimeException( "Internal error, window resized method not found"); } } /** * Resize events are fired whenever the client-side fires a resize-event * (e.g. the browser window is resized). The frequency may vary across * browsers. */ public class ResizeEvent extends Component.Event { /** * * @param source */ public ResizeEvent(Component source) { super(source); } /** * Get the window form which this event originated * * @return the window */ public Window getWindow() { return (Window) getSource(); } } /** * Listener for window resize events. * * @see com.vaadin.ui.Window.ResizeEvent */ public interface ResizeListener extends Serializable { public void windowResized(ResizeEvent e); } /** * Add a resize listener. * * @param listener */ public void addListener(ResizeListener listener) { addListener(ResizeEvent.class, listener, WINDOW_RESIZE_METHOD); } /** * Remove a resize listener. * * @param listener */ public void removeListener(ResizeListener listener) { removeListener(ResizeEvent.class, this); } /** * Fire the resize event. */ protected void fireResize() { fireEvent(new ResizeEvent(this)); } private void attachWindow(Window w) { subwindows.add(w); w.setParent(this); requestRepaint(); } /** * Adds a window inside another window. * *

* Adding windows inside another window creates "subwindows". These windows * should not be added to application directly and are not accessible * directly with any url. Addding windows implicitly sets their parents. *

* *

* Only one level of subwindows are supported. Thus you can add windows * inside such windows whose parent is null. *

* * @param window * @throws IllegalArgumentException * if a window is added inside non-application level window. * @throws NullPointerException * if the given Window is null. */ public void addWindow(Window window) throws IllegalArgumentException, NullPointerException { if (window == null) { throw new NullPointerException("Argument must not be null"); } if (window.getApplication() != null) { throw new IllegalArgumentException( "Window was already added to application" + " - it can not be added to another window also."); } else if (getParent() != null) { throw new IllegalArgumentException( "You can only add windows inside application-level windows."); } else if (window.subwindows.size() > 0) { throw new IllegalArgumentException( "Only one level of subwindows are supported."); } attachWindow(window); } /** * Remove the given subwindow from this window. * * Since Vaadin 6.5, {@link CloseListener}s are called also when explicitly * removing a window by calling this method. * * Since Vaadin 6.5, returns a boolean indicating if the window was removed * or not. * * @param window * Window to be removed. * @return true if the subwindow was removed, false otherwise */ public boolean removeWindow(Window window) { if (!subwindows.remove(window)) { // Window window is not a subwindow of this window. return false; } window.setParent(null); window.fireClose(); requestRepaint(); return true; } private Integer bringToFront = null; /* * This sequesnce is used to keep the right order of windows if multiple * windows are brought to front in a single changeset. Incremented and saved * by childwindows. If sequence is not used, the order is quite random * (depends on the order getting to dirty list. e.g. which window got * variable changes). */ private int bringToFrontSequence = 0; /** * If there are currently several sub windows visible, calling this method * makes this window topmost. *

* This method can only be called if this window is a sub window and * connected a top level window. Else an illegal state exception is thrown. * Also if there are modal windows and this window is not modal, and illegal * state exception is thrown. *

* Note, this API works on sub windows only. Browsers can't reorder * OS windows. */ public void bringToFront() { Window parent = getParent(); if (parent == null) { throw new IllegalStateException( "Window must be attached to parent before calling bringToFront method."); } for (Window w : parent.getChildWindows()) { if (w.isModal() && !isModal()) { throw new IllegalStateException( "There are modal windows currently visible, non-modal window cannot be brought to front."); } } bringToFront = getParent().bringToFrontSequence++; requestRepaint(); } /** * Get the set of all child windows. * * @return Set of child windows. */ public Set getChildWindows() { return Collections.unmodifiableSet(subwindows); } /** * Sets sub-window modal, so that widgets behind it cannot be accessed. * Note: affects sub-windows only. * * @param modality * true if modality is to be turned on */ public void setModal(boolean modality) { modal = modality; center(); requestRepaint(); } /** * @return true if this window is modal. */ public boolean isModal() { return modal; } /** * Sets sub-window resizable. Note: affects sub-windows only. * * @param resizable * true if resizability is to be turned on */ public void setResizable(boolean resizeability) { resizable = resizeability; requestRepaint(); } /** * * @return true if window is resizable by the end-user, otherwise false. */ public boolean isResizable() { return resizable; } /** * Request to center this window on the screen. Note: affects * sub-windows only. */ public void center() { centerRequested = true; requestRepaint(); } /** * Shows a notification message on the middle of the window. The message * automatically disappears ("humanized message"). * * @see #showNotification(com.vaadin.ui.Window.Notification) * @see Notification * * @param caption * The message */ public void showNotification(String caption) { addNotification(new Notification(caption)); } /** * Shows a notification message the window. The position and behavior of the * message depends on the type, which is one of the basic types defined in * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE. * * @see #showNotification(com.vaadin.ui.Window.Notification) * @see Notification * * @param caption * The message * @param type * The message type */ public void showNotification(String caption, int type) { addNotification(new Notification(caption, type)); } /** * Shows a notification consisting of a bigger caption and a smaller * description on the middle of the window. The message automatically * disappears ("humanized message"). * * @see #showNotification(com.vaadin.ui.Window.Notification) * @see Notification * * @param caption * The caption of the message * @param description * The message description * */ public void showNotification(String caption, String description) { addNotification(new Notification(caption, description)); } /** * Shows a notification consisting of a bigger caption and a smaller * description. The position and behavior of the message depends on the * type, which is one of the basic types defined in {@link Notification}, * for instance Notification.TYPE_WARNING_MESSAGE. * * @see #showNotification(com.vaadin.ui.Window.Notification) * @see Notification * * @param caption * The caption of the message * @param description * The message description * @param type * The message type */ public void showNotification(String caption, String description, int type) { addNotification(new Notification(caption, description, type)); } /** * Shows a notification message. * * @see Notification * @see #showNotification(String) * @see #showNotification(String, int) * @see #showNotification(String, String) * @see #showNotification(String, String, int) * * @param notification * The notification message to show */ public void showNotification(Notification notification) { addNotification(notification); } private void addNotification(Notification notification) { if (notifications == null) { notifications = new LinkedList(); } notifications.add(notification); requestRepaint(); } /** * This method is used by Component.Focusable objects to request focus to * themselves. Focus renders must be handled at window level (instead of * Component.Focusable) due we want the last focused component to be focused * in client too. Not the one that is rendered last (the case we'd get if * implemented in Focusable only). * * To focus component from Vaadin application, use Focusable.focus(). See * {@link Focusable}. * * @param focusable * to be focused on next paint */ void setFocusedComponent(Focusable focusable) { if (getParent() != null) { // focus is handled by main windows (getParent()).setFocusedComponent(focusable); } else { pendingFocus = focusable; requestRepaint(); } } /** * A notification message, used to display temporary messages to the user - * for example "Document saved", or "Save failed". *

* The notification message can consist of several parts: caption, * description and icon. It is usually used with only caption - one should * be wary of filling the notification with too much information. *

*

* The notification message tries to be as unobtrusive as possible, while * still drawing needed attention. There are several basic types of messages * that can be used in different situations: *

    *
  • TYPE_HUMANIZED_MESSAGE fades away quickly as soon as the user uses * the mouse or types something. It can be used to show fairly unimportant * messages, such as feedback that an operation succeeded ("Document Saved") * - the kind of messages the user ignores once the application is familiar. *
  • *
  • TYPE_WARNING_MESSAGE is shown for a short while after the user uses * the mouse or types something. It's default style is also more noticeable * than the humanized message. It can be used for messages that do not * contain a lot of important information, but should be noticed by the * user. Despite the name, it does not have to be a warning, but can be used * instead of the humanized message whenever you want to make the message a * little more noticeable.
  • *
  • TYPE_ERROR_MESSAGE requires to user to click it before disappearing, * and can be used for critical messages.
  • *
  • TYPE_TRAY_NOTIFICATION is shown for a while in the lower left corner * of the window, and can be used for "convenience notifications" that do * not have to be noticed immediately, and should not interfere with the * current task - for instance to show "You have a new message in your * inbox" while the user is working in some other area of the application.
  • *
*

*

* In addition to the basic pre-configured types, a Notification can also be * configured to show up in a custom position, for a specified time (or * until clicked), and with a custom stylename. An icon can also be added. *

* */ public static class Notification implements Serializable { public static final int TYPE_HUMANIZED_MESSAGE = 1; public static final int TYPE_WARNING_MESSAGE = 2; public static final int TYPE_ERROR_MESSAGE = 3; public static final int TYPE_TRAY_NOTIFICATION = 4; public static final int POSITION_CENTERED = 1; public static final int POSITION_CENTERED_TOP = 2; public static final int POSITION_CENTERED_BOTTOM = 3; public static final int POSITION_TOP_LEFT = 4; public static final int POSITION_TOP_RIGHT = 5; public static final int POSITION_BOTTOM_LEFT = 6; public static final int POSITION_BOTTOM_RIGHT = 7; public static final int DELAY_FOREVER = -1; public static final int DELAY_NONE = 0; private String caption; private String description; private Resource icon; private int position = POSITION_CENTERED; private int delayMsec = 0; private String styleName; /** * Creates a "humanized" notification message. * * @param caption * The message to show */ public Notification(String caption) { this(caption, null, TYPE_HUMANIZED_MESSAGE); } /** * Creates a notification message of the specified type. * * @param caption * The message to show * @param type * The type of message */ public Notification(String caption, int type) { this(caption, null, type); } /** * Creates a "humanized" notification message with a bigger caption and * smaller description. * * @param caption * The message caption * @param description * The message description */ public Notification(String caption, String description) { this(caption, description, TYPE_HUMANIZED_MESSAGE); } /** * Creates a notification message of the specified type, with a bigger * caption and smaller description. * * @param caption * The message caption * @param description * The message description * @param type * The type of message */ public Notification(String caption, String description, int type) { this.caption = caption; this.description = description; setType(type); } private void setType(int type) { switch (type) { case TYPE_WARNING_MESSAGE: delayMsec = 1500; styleName = "warning"; break; case TYPE_ERROR_MESSAGE: delayMsec = -1; styleName = "error"; break; case TYPE_TRAY_NOTIFICATION: delayMsec = 3000; position = POSITION_BOTTOM_RIGHT; styleName = "tray"; case TYPE_HUMANIZED_MESSAGE: default: break; } } /** * Gets the caption part of the notification message. * * @return The message caption */ public String getCaption() { return caption; } /** * Sets the caption part of the notification message * * @param caption * The message caption */ public void setCaption(String caption) { this.caption = caption; } /** * @deprecated Use {@link #getDescription()} instead. * @return */ @Deprecated public String getMessage() { return description; } /** * @deprecated Use {@link #setDescription(String)} instead. * @param description */ @Deprecated public void setMessage(String description) { this.description = description; } /** * Gets the description part of the notification message. * * @return The message description. */ public String getDescription() { return description; } /** * Sets the description part of the notification message. * * @param description */ public void setDescription(String description) { this.description = description; } /** * Gets the position of the notification message. * * @return The position */ public int getPosition() { return position; } /** * Sets the position of the notification message. * * @param position * The desired notification position */ public void setPosition(int position) { this.position = position; } /** * Gets the icon part of the notification message. * * @return The message icon */ public Resource getIcon() { return icon; } /** * Sets the icon part of the notification message. * * @param icon * The desired message icon */ public void setIcon(Resource icon) { this.icon = icon; } /** * Gets the delay before the notification disappears. * * @return the delay in msec, -1 indicates the message has to be * clicked. */ public int getDelayMsec() { return delayMsec; } /** * Sets the delay before the notification disappears. * * @param delayMsec * the desired delay in msec, -1 to require the user to click * the message */ public void setDelayMsec(int delayMsec) { this.delayMsec = delayMsec; } /** * Sets the style name for the notification message. * * @param styleName * The desired style name. */ public void setStyleName(String styleName) { this.styleName = styleName; } /** * Gets the style name for the notification message. * * @return */ public String getStyleName() { return styleName; } } /** * Executes JavaScript in this window. * *

* This method allows one to inject javascript from the server to client. A * client implementation is not required to implement this functionality, * but currently all web-based clients do implement this. *

* *

* Executing javascript this way often leads to cross-browser compatibility * issues and regressions that are hard to resolve. Use of this method * should be avoided and instead it is recommended to create new widgets * with GWT. For more info on creating own, reusable client-side widgets in * Java, read the corresponding chapter in Book of Vaadin. *

* * @param script * JavaScript snippet that will be executed. */ public void executeJavaScript(String script) { if (getParent() != null) { throw new UnsupportedOperationException( "Only application level windows can execute javascript."); } if (jsExecQueue == null) { jsExecQueue = new ArrayList(); } jsExecQueue.add(script); requestRepaint(); } /** * Returns the closable status of the sub window. If a sub window is * closable it typically shows an X in the upper right corner. Clicking on * the X sends a close event to the server. Setting closable to false will * remove the X from the sub window and prevent the user from closing the * window. * * Note! For historical reasons readonly controls the closability of the sub * window and therefore readonly and closable affect each other. Setting * readonly to true will set closable to false and vice versa. *

* Closable only applies to sub windows, not to browser level windows. * * @return true if the sub window can be closed by the user. */ public boolean isClosable() { return !isReadOnly(); } /** * Sets the closable status for the sub window. If a sub window is closable * it typically shows an X in the upper right corner. Clicking on the X * sends a close event to the server. Setting closable to false will remove * the X from the sub window and prevent the user from closing the window. * * Note! For historical reasons readonly controls the closability of the sub * window and therefore readonly and closable affect each other. Setting * readonly to true will set closable to false and vice versa. *

* Closable only applies to sub windows, not to browser level windows. * * @param closable * determines if the sub window can be closed by the user. */ public void setClosable(boolean closable) { setReadOnly(!closable); } /** * Indicates whether a sub window can be dragged or not. By default a sub * window is draggable. *

* Draggable only applies to sub windows, not to browser level windows. * * @param draggable * true if the sub window can be dragged by the user */ public boolean isDraggable() { return draggable; } /** * Enables or disables that a sub window can be dragged (moved) by the user. * By default a sub window is draggable. *

* Draggable only applies to sub windows, not to browser level windows. * * @param draggable * true if the sub window can be dragged by the user */ public void setDraggable(boolean draggable) { this.draggable = draggable; requestRepaint(); } /* * Actions */ protected CloseShortcut closeShortcut; /** * Makes is possible to close the window by pressing the given * {@link KeyCode} and (optional) {@link ModifierKey}s.
* Note that this shortcut only reacts while the window has focus, closing * itself - if you want to close a subwindow from a parent window, use * {@link #addAction(com.vaadin.event.Action)} of the parent window instead. * * @param keyCode * the keycode for invoking the shortcut * @param modifiers * the (optional) modifiers for invoking the shortcut, null for * none */ public void setCloseShortcut(int keyCode, int... modifiers) { if (closeShortcut != null) { removeAction(closeShortcut); } closeShortcut = new CloseShortcut(this, keyCode, modifiers); addAction(closeShortcut); } /** * Removes the keyboard shortcut previously set with * {@link #setCloseShortcut(int, int...)}. */ public void removeCloseShortcut() { if (closeShortcut != null) { removeAction(closeShortcut); closeShortcut = null; } } /** * A {@link ShortcutListener} specifically made to define a keyboard * shortcut that closes the window. * *

     * 
     *  // within the window using helper
     *  subWindow.setCloseShortcut(KeyCode.ESCAPE, null);
     *  
     *  // or globally
     *  getWindow().addAction(new Window.CloseShortcut(subWindow, KeyCode.ESCAPE));
     * 
     * 
* */ public static class CloseShortcut extends ShortcutListener { protected Window window; /** * Creates a keyboard shortcut for closing the given window using the * shorthand notation defined in {@link ShortcutAction}. * * @param window * to be closed when the shortcut is invoked * @param shorthandCaption * the caption with shortcut keycode and modifiers indicated */ public CloseShortcut(Window window, String shorthandCaption) { super(shorthandCaption); this.window = window; } /** * Creates a keyboard shortcut for closing the given window using the * given {@link KeyCode} and {@link ModifierKey}s. * * @param window * to be closed when the shortcut is invoked * @param keyCode * KeyCode to react to * @param modifiers * optional modifiers for shortcut */ public CloseShortcut(Window window, int keyCode, int... modifiers) { super(null, keyCode, modifiers); this.window = window; } /** * Creates a keyboard shortcut for closing the given window using the * given {@link KeyCode}. * * @param window * to be closed when the shortcut is invoked * @param keyCode * KeyCode to react to */ public CloseShortcut(Window window, int keyCode) { this(window, keyCode, null); } @Override public void handleAction(Object sender, Object target) { window.close(); } } /** * Note, that focus/blur listeners in Window class are only supported by sub * windows. Also note that Window is not considered focused if its contained * component currently has focus. * * @see com.vaadin.event.FieldEvents.FocusNotifier#addListener(com.vaadin.event.FieldEvents.FocusListener) */ public void addListener(FocusListener listener) { addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener, FocusListener.focusMethod); } public void removeListener(FocusListener listener) { removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener); } /** * Note, that focus/blur listeners in Window class are only supported by sub * windows. Also note that Window is not considered focused if its contained * component currently has focus. * * @see com.vaadin.event.FieldEvents.BlurNotifier#addListener(com.vaadin.event.FieldEvents.BlurListener) */ public void addListener(BlurListener listener) { addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener, BlurListener.blurMethod); } public void removeListener(BlurListener listener) { removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); } }