/* @VaadinApache2LicenseForJavaFiles@ */ 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.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.vaadin.Application; import com.vaadin.annotations.EagerInit; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.ActionManager; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.MouseEvents.ClickListener; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.gwt.client.ComponentState; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.notification.VNotification; import com.vaadin.terminal.gwt.client.ui.root.RootServerRpc; import com.vaadin.terminal.gwt.client.ui.root.RootState; import com.vaadin.terminal.gwt.client.ui.root.VRoot; import com.vaadin.tools.ReflectTools; import com.vaadin.ui.Window.CloseListener; /** * The topmost component in any component hierarchy. There is one root for every * Vaadin instance in a browser window. A root may either represent an entire * browser window (or tab) or some part of a html page where a Vaadin * application is embedded. *
* The root is the server side entry point for various client side features that * are not represented as components added to a layout, e.g notifications, sub * windows, and executing javascript in the browser. *
** When a new application instance is needed, typically because the user opens * the application in a browser window, * {@link Application#gerRoot(WrappedRequest)} is invoked to get a root. That * method does by default create a root according to the * {@value Application#ROOT_PARAMETER} parameter from web.xml. *
** After a root has been created by the application, it is initialized using * {@link #init(WrappedRequest)}. This method is intended to be overridden by * the developer to add components to the user interface and initialize * non-component functionality. The component hierarchy is initialized by * passing a {@link ComponentContainer} with the main layout of the view to * {@link #setContent(ComponentContainer)}. *
*
* If a {@link EagerInit} annotation is present on a class extending
* Root
, the framework will use a faster initialization method
* which will not ensure that {@link BrowserDetails} are present in the
* {@link WrappedRequest} passed to the init method.
*
* 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; } /** * 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) { this.name = name; // 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"); } } /** * 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() { Application application = getApplication(); if (application == null) { return null; } try { return new URL(application.getURL(), getName() + "/"); } catch (MalformedURLException e) { throw new RuntimeException( "Internal problem getting window URL, please report"); } } } private static final Method FRAGMENT_CHANGED_METHOD; static { try { FRAGMENT_CHANGED_METHOD = FragmentChangedListener.class .getDeclaredMethod("fragmentChanged", new Class[] { FragmentChangedEvent.class }); } catch (final java.lang.NoSuchMethodException e) { // This should never happen throw new java.lang.RuntimeException( "Internal error finding methods in FragmentChangedListener"); } } /** * A border style used for opening resources in a window without a border. */ public static final int BORDER_NONE = 0; /** * A border style used for opening resources in a window with a minimal * border. */ public static final int BORDER_MINIMAL = 1; /** * A border style that indicates that the default border style should be * used when opening resources. */ public static final int BORDER_DEFAULT = 2; /** * The application to which this root belongs */ private Application application; /** * A list of notifications that are waiting to be sent to the client. * Cleared (set to null) when the notifications have been sent. */ private Listnull
application.
* * This method is mainly intended for internal use by the framework. *
* * @param application * the application to set * * @throws IllegalStateException * if the application has already been set * * @see #getApplication() */ public void setApplication(Application application) { if ((application == null) == (this.application == null)) { throw new IllegalStateException("Application has already been set"); } else { this.application = application; } if (application != null) { attach(); } else { detach(); } } /** * Sets the id of this root within its application. The root id is used to * route requests to the right root. ** This method is mainly intended for internal use by the framework. *
* * @param rootId * the id of this root * * @throws IllegalStateException * if the root id has already been set * * @see #getRootId() */ public void setRootId(int rootId) { if (this.rootId != -1) { throw new IllegalStateException("Root id has already been defined"); } this.rootId = rootId; } /** * Gets the id of the root, used to identify this root within its * application when processing requests. The root id should be present in * every request to the server that originates from this root. * {@link Application#getRootForRequest(WrappedRequest)} uses this id to * find the route to which the request belongs. * * @return */ public int getRootId() { return rootId; } /** * Adds a window as a subwindow inside this root. To open a new browser * window or tab, you should instead use {@link open(Resource)} with an url * pointing to this application and ensure * {@link Application#getRoot(WrappedRequest)} returns an appropriate root * for the request. * * @param window * @throws IllegalArgumentException * if the window is already added to an application * @throws NullPointerException * if the givenWindow
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 is already attached to an application.");
}
attachWindow(window);
}
/**
* Helper method to attach a window.
*
* @param w
* the window to add
*/
private void attachWindow(Window w) {
windows.add(w);
w.setParent(this);
requestRepaint();
}
/**
* Remove the given subwindow from this root.
*
* 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 (!windows.remove(window)) {
// Window window is not a subwindow of this root.
return false;
}
window.setParent(null);
window.fireClose();
requestRepaint();
return true;
}
/**
* Gets all the windows added to this root.
*
* @return an unmodifiable collection of windows
*/
public Collection* 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 (jsExecQueue == null) { jsExecQueue = new ArrayList* The {@link WrappedRequest} can be used to get information about the * request that caused this root to be created. By default, the * {@link BrowserDetails} will be available in the request. If the browser * details are not required, loading the application in the browser can take * some shortcuts giving a faster initial rendering. This can be indicated * by adding the {@link EagerInit} annotation to the Root class. *
* * @param request * the wrapped request that caused this root to be created */ protected abstract void init(WrappedRequest request); /** * Sets the thread local for the current root. This method is used by the * framework to set the current application whenever a new request is * processed and it is cleared when the request has been processed. ** The application developer can also use this method to define the current * root outside the normal request handling, e.g. when initiating custom * background threads. *
* * @param root * the root to register as the current root * * @see #getCurrentRoot() * @see ThreadLocal */ public static void setCurrentRoot(Root root) { currentRoot.set(root); } /** * Gets the currently used root. The current root is automatically defined * when processing requests to the server. In other cases, (e.g. from * background threads), the current root is not automatically defined. * * @return the current root instance if available, otherwise *null
*
* @see #setCurrentRoot(Root)
*/
public static Root getCurrentRoot() {
return currentRoot.get();
}
/**
* Opens the given resource in this root. The contents of this Root is
* replaced by the {@code Resource}.
*
* @param resource
* the resource to show in this root
*/
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(); } /** * 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 BORDER_MINIMAL: target.addAttribute("border", "minimal"); break; case BORDER_NONE: target.addAttribute("border", "none"); break; } target.endTag("open"); } } public void setScrollTop(int scrollTop) { throw new RuntimeException("Not yet implemented"); } @Override protected ActionManager getActionManager() { if (actionManager == null) { actionManager = new ActionManager(this); } return actionManager; } public
* Default value: false
*
* @param resizeLazy
* true to use a delay before recalculating sizes, false to
* calculate immediately.
*/
public void setResizeLazy(boolean resizeLazy) {
this.resizeLazy = resizeLazy;
requestRepaint();
}
/**
* Checks whether lazy resize is enabled.
*
* @return true
if lazy resize is enabled, false
* if lazy resize is not enabled
*/
public boolean isResizeLazy() {
return resizeLazy;
}
/**
* Add a click listener to the Root. The listener is called whenever the
* user clicks inside the Root. Also when the click targets a component
* inside the Root, provided the targeted component does not prevent the
* click event from propagating.
*
* Use {@link #removeListener(ClickListener)} to remove the listener.
*
* @param listener
* The listener to add
*/
public void addListener(ClickListener listener) {
addListener(CLICK_EVENT_ID, ClickEvent.class, listener,
ClickListener.clickMethod);
}
/**
* Remove a click listener from the Root. The listener should earlier have
* been added using {@link #addListener(ClickListener)}.
*
* @param listener
* The listener to remove
*/
public void removeListener(ClickListener listener) {
removeListener(CLICK_EVENT_ID, ClickEvent.class, listener);
}
public void addListener(FragmentChangedListener listener) {
addListener(FragmentChangedEvent.class, listener,
FRAGMENT_CHANGED_METHOD);
}
public void removeListener(FragmentChangedListener listener) {
removeListener(FragmentChangedEvent.class, listener,
FRAGMENT_CHANGED_METHOD);
}
/**
* Sets URI fragment. Optionally fires a {@link FragmentChangedEvent}
*
* @param newFragment
* id of the new fragment
* @param fireEvent
* true to fire event
* @see FragmentChangedEvent
* @see FragmentChangedListener
*/
public void setFragment(String newFragment, boolean fireEvents) {
if (newFragment == null) {
throw new NullPointerException("The fragment may not be null");
}
if (!newFragment.equals(fragment)) {
fragment = newFragment;
if (fireEvents) {
fireEvent(new FragmentChangedEvent(this, newFragment));
}
requestRepaint();
}
}
/**
* Sets URI fragment. This method fires a {@link FragmentChangedEvent}
*
* @param newFragment
* id of the new fragment
* @see FragmentChangedEvent
* @see FragmentChangedListener
*/
public void setFragment(String newFragment) {
setFragment(newFragment, true);
}
/**
* Gets currently set URI fragment.
*
* To listen changes in fragment, hook a {@link FragmentChangedListener}. * * @return the current fragment in browser uri or null if not known */ public String getFragment() { return fragment; } /** * Adds a new {@link BrowserWindowResizeListener} to this root. The listener * will be notified whenever the browser window within which this root * resides is resized. * * @param resizeListener * the listener to add * * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent) * @see #setResizeLazy(boolean) */ public void addListener(BrowserWindowResizeListener resizeListener) { addListener(BrowserWindowResizeEvent.class, resizeListener, BROWSWER_RESIZE_METHOD); } /** * Removes a {@link BrowserWindowResizeListener} from this root. The * listener will no longer be notified when the browser window is resized. * * @param resizeListener * the listener to remove */ public void removeListener(BrowserWindowResizeListener resizeListener) { removeListener(BrowserWindowResizeEvent.class, resizeListener, BROWSWER_RESIZE_METHOD); } /** * Gets the last known height of the browser window in which this root * resides. * * @return the browser window height in pixels */ public int getBrowserWindowHeight() { return browserWindowHeight; } /** * Gets the last known width of the browser window in which this root * resides. * * @return the browser window width in pixels */ public int getBrowserWindowWidth() { return browserWindowWidth; } /** * Notifies the child components and windows that the root is attached to * the application. */ @Override public void attach() { super.attach(); for (Window w : windows) { w.attach(); } } /** * Notifies the child components and windows that the root is detached from * the application. */ @Override public void detach() { super.detach(); for (Window w : windows) { w.detach(); } } @Override public boolean isConnectorEnabled() { // TODO How can a Root be invisible? What does it mean? return isVisible() && isEnabled(); } public DirtyConnectorTracker getDirtyConnectorTracker() { return dirtyConnectorTracker; } public void componentAttached(Component component) { getDirtyConnectorTracker().componentAttached(component); } public void componentDetached(Component component) { getDirtyConnectorTracker().componentDetached(component); } }