diff options
Diffstat (limited to 'src')
484 files changed, 40401 insertions, 28944 deletions
diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java index 9fb4cfe7fa..4da1d52c00 100644 --- a/src/com/vaadin/Application.java +++ b/src/com/vaadin/Application.java @@ -4,35 +4,60 @@ package com.vaadin; +import java.io.IOException; import java.io.Serializable; +import java.lang.annotation.Annotation; import java.net.SocketException; import java.net.URL; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.EventObject; +import java.util.HashMap; +import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; - +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.vaadin.annotations.EagerInit; +import com.vaadin.annotations.Theme; +import com.vaadin.annotations.Widgetset; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterFactory; +import com.vaadin.data.util.converter.DefaultConverterFactory; import com.vaadin.service.ApplicationContext; +import com.vaadin.terminal.AbstractErrorMessage; import com.vaadin.terminal.ApplicationResource; -import com.vaadin.terminal.DownloadStream; -import com.vaadin.terminal.ErrorMessage; -import com.vaadin.terminal.ParameterHandler; -import com.vaadin.terminal.SystemError; +import com.vaadin.terminal.CombinedRequest; +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.RequestHandler; import com.vaadin.terminal.Terminal; -import com.vaadin.terminal.URIHandler; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedRequest.BrowserDetails; +import com.vaadin.terminal.WrappedResponse; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; -import com.vaadin.terminal.gwt.server.PortletApplicationContext; +import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractField; +import com.vaadin.ui.Component; +import com.vaadin.ui.Root; +import com.vaadin.ui.Table; import com.vaadin.ui.Window; /** @@ -89,37 +114,317 @@ import com.vaadin.ui.Window; * @since 3.0 */ @SuppressWarnings("serial") -public abstract class Application implements URIHandler, - Terminal.ErrorListener, Serializable { - - private final static Logger logger = Logger.getLogger(Application.class - .getName()); +public class Application implements Terminal.ErrorListener, Serializable { /** - * Id use for the next window that is opened. Access to this must be - * synchronized. + * The name of the parameter that is by default used in e.g. web.xml to + * define the name of the default {@link Root} class. */ - private int nextWindowId = 1; + public static final String ROOT_PARAMETER = "root"; /** - * Application context the application is running in. + * A special application designed to help migrating applications from Vaadin + * 6 to Vaadin 7. The legacy application supports setting a main window, + * adding additional browser level windows and defining the theme for the + * entire application. + * + * @deprecated This class is only intended to ease migration and should not + * be used for new projects. + * + * @since 7.0 */ - private ApplicationContext context; + @Deprecated + public static class LegacyApplication extends Application { + /** + * Ignore initial / and then get everything up to the next / + */ + private static final Pattern WINDOW_NAME_PATTERN = Pattern + .compile("^/?([^/]+).*"); + + private Root.LegacyWindow mainWindow; + private String theme; + + private Map<String, Root.LegacyWindow> legacyRootNames = new HashMap<String, Root.LegacyWindow>(); + + /** + * Sets the main window of this application. Setting window as a main + * window of this application also adds the window to this application. + * + * @param mainWindow + * the root to set as the default window + */ + public void setMainWindow(Root.LegacyWindow mainWindow) { + if (this.mainWindow != null) { + throw new IllegalStateException( + "mainWindow has already been set"); + } + if (mainWindow.getApplication() == null) { + mainWindow.setApplication(this); + } else if (mainWindow.getApplication() != this) { + throw new IllegalStateException( + "mainWindow is attached to another application"); + } + this.mainWindow = mainWindow; + } + + /** + * Gets the mainWindow of the application. + * + * <p> + * The main window is the window attached to the application URL ( + * {@link #getURL()}) and thus which is show by default to the user. + * </p> + * <p> + * Note that each application must have at least one main window. + * </p> + * + * @return the root used as the default window + */ + public Root.LegacyWindow getMainWindow() { + return mainWindow; + } + + /** + * This implementation simulates the way of finding a window for a + * request by extracting a window name from the requested path and + * passes that name to {@link #getWindow(String)}. + * + * {@inheritDoc} + * + * @see #getWindow(String) + * @see Application#getRoot(WrappedRequest) + */ + @Override + public Root.LegacyWindow getRoot(WrappedRequest request) { + String pathInfo = request.getRequestPathInfo(); + String name = null; + if (pathInfo != null && pathInfo.length() > 0) { + Matcher matcher = WINDOW_NAME_PATTERN.matcher(pathInfo); + if (matcher.matches()) { + // Skip the initial slash + name = matcher.group(1); + } + } + Root.LegacyWindow window = getWindow(name); + if (window != null) { + return window; + } + return mainWindow; + } + + /** + * Sets the application's theme. + * <p> + * Note that this theme can be overridden for a specific root with + * {@link Application#getThemeForRoot(Root)}. Setting theme to be + * <code>null</code> selects the default theme. For the available theme + * names, see the contents of the VAADIN/themes directory. + * </p> + * + * @param theme + * the new theme for this application. + */ + public void setTheme(String theme) { + this.theme = theme; + } + + /** + * Gets the application's theme. The application's theme is the default + * theme used by all the roots for which a theme is not explicitly + * defined. If the application theme is not explicitly set, + * <code>null</code> is returned. + * + * @return the name of the application's theme. + */ + public String getTheme() { + return theme; + } + + /** + * This implementation returns the theme that has been set using + * {@link #setTheme(String)} + * <p> + * {@inheritDoc} + */ + @Override + public String getThemeForRoot(Root root) { + return theme; + } + + /** + * <p> + * Gets a root by name. Returns <code>null</code> if the application is + * not running or it does not contain a window corresponding to the + * name. + * </p> + * + * @param name + * the name of the requested window + * @return a root corresponding to the name, or <code>null</code> to use + * the default window + */ + public Root.LegacyWindow getWindow(String name) { + return legacyRootNames.get(name); + } + + /** + * Counter to get unique names for windows with no explicit name + */ + private int namelessRootIndex = 0; + + /** + * Adds a new browser level window to this application. Please note that + * Root doesn't have a name that is used in the URL - to add a named + * window you should instead use {@link #addWindow(Root, String)} + * + * @param root + * the root window to add to the application + * @return returns the name that has been assigned to the window + * + * @see #addWindow(Root, String) + */ + public void addWindow(Root.LegacyWindow root) { + if (root.getName() == null) { + String name = Integer.toString(namelessRootIndex++); + root.setName(name); + } + + legacyRootNames.put(root.getName(), root); + root.setApplication(this); + } + + /** + * Removes the specified window from the application. This also removes + * all name mappings for the window (see + * {@link #addWindow(Root, String) and #getWindowName(Root)}. + * + * <p> + * Note that removing window from the application does not close the + * browser window - the window is only removed from the server-side. + * </p> + * + * @param root + * the root to remove + */ + public void removeWindow(Root.LegacyWindow root) { + for (Entry<String, Root.LegacyWindow> entry : legacyRootNames + .entrySet()) { + if (entry.getValue() == root) { + legacyRootNames.remove(entry.getKey()); + } + } + } + + /** + * Gets the set of windows contained by the application. + * + * <p> + * Note that the returned set of windows can not be modified. + * </p> + * + * @return the unmodifiable collection of windows. + */ + public Collection<Root.LegacyWindow> getWindows() { + return Collections.unmodifiableCollection(legacyRootNames.values()); + } + } /** - * The current user or <code>null</code> if no user has logged in. + * An event sent to {@link #start(ApplicationStartEvent)} when a new + * Application is being started. + * + * @since 7.0 */ - private Object user; + public static class ApplicationStartEvent implements Serializable { + private final URL applicationUrl; + + private final Properties applicationProperties; + + private final ApplicationContext context; + + private final boolean productionMode; + + /** + * @param applicationUrl + * the URL the application should respond to. + * @param applicationProperties + * the Application properties as specified by the deployment + * configuration. + * @param context + * the context application will be running in. + * @param productionMode + * flag indicating whether the application is running in + * production mode. + */ + public ApplicationStartEvent(URL applicationUrl, + Properties applicationProperties, ApplicationContext context, + boolean productionMode) { + this.applicationUrl = applicationUrl; + this.applicationProperties = applicationProperties; + this.context = context; + this.productionMode = productionMode; + } + + /** + * Gets the URL the application should respond to. + * + * @return the URL the application should respond to or + * <code>null</code> if the URL is not defined. + * + * @see Application#getURL() + */ + public URL getApplicationUrl() { + return applicationUrl; + } + + /** + * Gets the Application properties as specified by the deployment + * configuration. + * + * @return the properties configured for the applciation. + * + * @see Application#getProperty(String) + */ + public Properties getApplicationProperties() { + return applicationProperties; + } + + /** + * Gets the context application will be running in. + * + * @return the context application will be running in. + * + * @see Application#getContext() + */ + public ApplicationContext getContext() { + return context; + } + + /** + * Checks whether the application is running in production mode. + * + * @return <code>true</code> if in production mode, else + * <code>false</code> + * + * @see Application#isProductionMode() + */ + public boolean isProductionMode() { + return productionMode; + } + } + + private final static Logger logger = Logger.getLogger(Application.class + .getName()); /** - * Mapping from window name to window instance. + * Application context the application is running in. */ - private final Hashtable<String, Window> windows = new Hashtable<String, Window>(); + private ApplicationContext context; /** - * Main window of the application. + * The current user or <code>null</code> if no user has logged in. */ - private Window mainWindow = null; + private Object user; /** * The application's URL. @@ -127,11 +432,6 @@ public abstract class Application implements URIHandler, private URL applicationUrl; /** - * Name of the theme currently used by the application. - */ - private String theme = null; - - /** * Application status. */ private volatile boolean applicationIsRunning = false; @@ -152,16 +452,6 @@ public abstract class Application implements URIHandler, private LinkedList<UserChangeListener> userChangeListeners = null; /** - * Window attach listeners. - */ - private LinkedList<WindowAttachListener> windowAttachListeners = null; - - /** - * Window detach listeners. - */ - private LinkedList<WindowDetachListener> windowDetachListeners = null; - - /** * Application resource mapping: key <-> resource. */ private final Hashtable<ApplicationResource, String> resourceKeyMap = new Hashtable<ApplicationResource, String>(); @@ -189,237 +479,28 @@ public abstract class Application implements URIHandler, private Terminal.ErrorListener errorHandler = this; /** - * <p> - * Gets a window by name. Returns <code>null</code> if the application is - * not running or it does not contain a window corresponding to the name. - * </p> - * - * <p> - * All windows can be referenced by their names in url - * <code>http://host:port/foo/bar/</code> where - * <code>http://host:port/foo/</code> is the application url as returned by - * getURL() and <code>bar</code> is the name of the window. - * </p> - * - * <p> - * One should note that this method can, as a side effect create new windows - * if needed by the application. This can be achieved by overriding the - * default implementation. - * </p> - * - * <p> - * If for some reason user opens another window with same url that is - * already open, the name is modified by adding a "_N" postfix to the name, - * where N is a running number starting from 1. One can decide to create - * another window-object for those windows (recommended) or to discard the - * postfix. If the user has two browser windows pointing to the same - * window-object on server, synchronization errors are likely to occur. - * </p> - * - * <p> - * If no browser-level windowing is used, all defaults are fine and this - * method can be left as is. In case browser-level windows are needed, it is - * recommended to create new window-objects on this method from their names - * if the super.getWindow() does not find existing windows. See below for - * implementation example: <code><pre> - // If we already have the requested window, use it - Window w = super.getWindow(name); - if (w == null) { - // If no window found, create it - w = new Window(name); - // set windows name to the one requested - w.setName(name); - // add it to this application - addWindow(w); - // ensure use of window specific url - w.open(new ExternalResource(w.getURL().toString())); - // add some content - w.addComponent(new Label("Test window")); - } - return w;</pre></code> - * </p> - * - * <p> - * <strong>Note</strong> that all returned Window objects must be added to - * this application instance. - * - * <p> - * The method should return null if the window does not exists (and is not - * created as a side-effect) or if the application is not running anymore. - * </p> - * - * @param name - * the name of the window. - * @return the window associated with the given URI or <code>null</code> + * The converter factory that is used to provide default converters for the + * application. */ - public Window getWindow(String name) { - - // For closed app, do not give any windows - if (!isRunning()) { - return null; - } - - // Gets the window by name - final Window window = windows.get(name); - - return window; - } - - /** - * Adds a new window to the application. - * - * <p> - * This implicitly invokes the - * {@link com.vaadin.ui.Window#setApplication(Application)} method. - * </p> - * - * <p> - * Note that all application-level windows can be accessed by their names in - * url <code>http://host:port/foo/bar/</code> where - * <code>http://host:port/foo/</code> is the application url as returned by - * getURL() and <code>bar</code> is the name of the window. Also note that - * not all windows should be added to application - one can also add windows - * inside other windows - these windows show as smaller windows inside those - * windows. - * </p> - * - * @param window - * the new <code>Window</code> to add. If the name of the window - * is <code>null</code>, an unique name is automatically given - * for the window. - * @throws IllegalArgumentException - * if a window with the same name as the new window already - * exists in the application. - * @throws NullPointerException - * if the given <code>Window</code> is <code>null</code>. - */ - public void addWindow(Window window) throws IllegalArgumentException, - NullPointerException { - - // Nulls can not be added to application - if (window == null) { - return; - } - - // Check that one is not adding a sub-window to application - if (window.getParent() != null) { - throw new IllegalArgumentException( - "Window was already added inside another window" - + " - it can not be added to application also."); - } - - // Gets the naming proposal from window - String name = window.getName(); - - // Checks that the application does not already contain - // window having the same name - if (name != null && windows.containsKey(name)) { - - // If the window is already added - if (window == windows.get(name)) { - return; - } - - // Otherwise complain - throw new IllegalArgumentException("Window with name '" - + window.getName() - + "' is already present in the application"); - } + private ConverterFactory converterFactory = new DefaultConverterFactory(); - // If the name of the window is null, the window is automatically named - if (name == null) { - boolean accepted = false; - while (!accepted) { + private LinkedList<RequestHandler> requestHandlers = new LinkedList<RequestHandler>(); - // Try another name - synchronized (this) { - name = String.valueOf(nextWindowId); - nextWindowId++; - } - - if (!windows.containsKey(name)) { - accepted = true; - } - } - window.setName(name); - } - - // Adds the window to application - windows.put(name, window); - window.setApplication(this); + private int nextRootId = 0; + private Map<Integer, Root> roots = new HashMap<Integer, Root>(); - fireWindowAttachEvent(window); - - // If no main window is set, declare the window to be main window - if (getMainWindow() == null) { - mainWindow = window; - } - } + private boolean productionMode = true; - /** - * Send information to all listeners about new Windows associated with this - * application. - * - * @param window - */ - private void fireWindowAttachEvent(Window window) { - // Fires the window attach event - if (windowAttachListeners != null) { - final Object[] listeners = windowAttachListeners.toArray(); - final WindowAttachEvent event = new WindowAttachEvent(window); - for (int i = 0; i < listeners.length; i++) { - ((WindowAttachListener) listeners[i]).windowAttached(event); - } - } - } + private final Map<String, Integer> retainOnRefreshRoots = new HashMap<String, Integer>(); /** - * Removes the specified window from the application. - * - * <p> - * Removing the main window of the Application also sets the main window to - * null. One must another window to be the main window after this with - * {@link #setMainWindow(Window)}. - * </p> - * + * Keeps track of which roots have been inited. * <p> - * Note that removing window from the application does not close the browser - * window - the window is only removed from the server-side. + * TODO Investigate whether this might be derived from the different states + * in getRootForRrequest. * </p> - * - * @param window - * the window to be removed. */ - public void removeWindow(Window window) { - if (window != null && windows.contains(window)) { - - // Removes the window from application - windows.remove(window.getName()); - - // If the window was main window, clear it - if (getMainWindow() == window) { - setMainWindow(null); - } - - // Removes the application from window - if (window.getApplication() == this) { - window.setApplication(null); - } - - fireWindowDetachEvent(window); - } - } - - private void fireWindowDetachEvent(Window window) { - // Fires the window detach event - if (windowDetachListeners != null) { - final Object[] listeners = windowDetachListeners.toArray(); - final WindowDetachEvent event = new WindowDetachEvent(window); - for (int i = 0; i < listeners.length; i++) { - ((WindowDetachListener) listeners[i]).windowDetached(event); - } - } - } + private Set<Integer> initedRoots = new HashSet<Integer>(); /** * Gets the user of the application. @@ -525,20 +606,16 @@ public abstract class Application implements URIHandler, * {@link javax.servlet.ServletContext}. * </p> * - * @param applicationUrl - * the URL the application should respond to. - * @param applicationProperties - * the Application properties as specified by the servlet - * configuration. - * @param context - * the context application will be running in. + * @param event + * the application start event containing details required for + * starting the application. * */ - public void start(URL applicationUrl, Properties applicationProperties, - ApplicationContext context) { - this.applicationUrl = applicationUrl; - properties = applicationProperties; - this.context = context; + public void start(ApplicationStartEvent event) { + applicationUrl = event.getApplicationUrl(); + productionMode = event.isProductionMode(); + properties = event.getApplicationProperties(); + context = event.getContext(); init(); applicationIsRunning = true; } @@ -560,106 +637,14 @@ public abstract class Application implements URIHandler, } /** - * Gets the set of windows contained by the application. - * - * <p> - * Note that the returned set of windows can not be modified. - * </p> - * - * @return the Unmodifiable collection of windows. - */ - public Collection<Window> getWindows() { - return Collections.unmodifiableCollection(windows.values()); - } - - /** * <p> * Main initializer of the application. The <code>init</code> method is * called by the framework when the application is started, and it should - * perform whatever initialization operations the application needs, such as - * creating windows and adding components to them. - * </p> - */ - public abstract void init(); - - /** - * Gets the application's theme. The application's theme is the default - * theme used by all the windows in it that do not explicitly specify a - * theme. If the application theme is not explicitly set, the - * <code>null</code> is returned. - * - * @return the name of the application's theme. - */ - public String getTheme() { - return theme; - } - - /** - * Sets the application's theme. - * <p> - * Note that this theme can be overridden in the the application level - * windows with {@link com.vaadin.ui.Window#setTheme(String)}. Setting theme - * to be <code>null</code> selects the default theme. For the available - * theme names, see the contents of the VAADIN/themes directory. - * </p> - * - * @param theme - * the new theme for this application. - */ - public void setTheme(String theme) { - // Collect list of windows not having the current or future theme - final LinkedList<Window> toBeUpdated = new LinkedList<Window>(); - final String oldAppTheme = getTheme(); - for (final Iterator<Window> i = getWindows().iterator(); i.hasNext();) { - final Window w = i.next(); - final String windowTheme = w.getTheme(); - if ((windowTheme == null) - || (!windowTheme.equals(theme) && windowTheme - .equals(oldAppTheme))) { - toBeUpdated.add(w); - } - } - - // Updates the theme - this.theme = theme; - - // Ask windows to update themselves - for (final Iterator<Window> i = toBeUpdated.iterator(); i.hasNext();) { - i.next().requestRepaint(); - } - } - - /** - * Gets the mainWindow of the application. - * - * <p> - * The main window is the window attached to the application URL ( - * {@link #getURL()}) and thus which is show by default to the user. - * </p> - * <p> - * Note that each application must have at least one main window. - * </p> - * - * @return the main window. - */ - public Window getMainWindow() { - return mainWindow; - } - - /** - * <p> - * Sets the mainWindow. If the main window is not explicitly set, the main - * window defaults to first created window. Setting window as a main window - * of this application also adds the window to this application. + * perform whatever initialization operations the application needs. * </p> - * - * @param mainWindow - * the mainWindow to set. */ - public void setMainWindow(Window mainWindow) { - - addWindow(mainWindow); - this.mainWindow = mainWindow; + public void init() { + // Default implementation does nothing } /** @@ -759,50 +744,6 @@ public abstract class Application implements URIHandler, } /** - * Application URI handling hub. - * - * <p> - * This method gets called by terminal. It has lots of duties like to pass - * uri handler to proper uri handlers registered to windows etc. - * </p> - * - * <p> - * In most situations developers should NOT OVERRIDE this method. Instead - * developers should implement and register uri handlers to windows. - * </p> - * - * @deprecated this method is called be the terminal implementation only and - * might be removed or moved in the future. Instead of - * overriding this method, add your {@link URIHandler} to a top - * level {@link Window} (eg. - * getMainWindow().addUriHanler(handler) instead. - */ - @Deprecated - public DownloadStream handleURI(URL context, String relativeUri) { - - if (this.context.isApplicationResourceURL(context, relativeUri)) { - - // Handles the resource request - final String key = this.context.getURLKey(context, relativeUri); - final ApplicationResource resource = keyResourceMap.get(key); - if (resource != null) { - DownloadStream stream = resource.getStream(); - if (stream != null) { - stream.setCacheTime(resource.getCacheTime()); - return stream; - } else { - return null; - } - } else { - // Resource requests override uri handling - return null; - } - } else { - return null; - } - } - - /** * Gets the default locale for this application. * * By default this is the preferred locale of the user using the @@ -1061,68 +1002,6 @@ public abstract class Application implements URIHandler, } /** - * Adds the window attach listener. - * - * Use this to get notifications each time a window is attached to the - * application with {@link #addWindow(Window)}. - * - * @param listener - * the window attach listener to add. - */ - public void addListener(WindowAttachListener listener) { - if (windowAttachListeners == null) { - windowAttachListeners = new LinkedList<WindowAttachListener>(); - } - windowAttachListeners.add(listener); - } - - /** - * Adds the window detach listener. - * - * Use this to get notifications each time a window is remove from the - * application with {@link #removeWindow(Window)}. - * - * @param listener - * the window detach listener to add. - */ - public void addListener(WindowDetachListener listener) { - if (windowDetachListeners == null) { - windowDetachListeners = new LinkedList<WindowDetachListener>(); - } - windowDetachListeners.add(listener); - } - - /** - * Removes the window attach listener. - * - * @param listener - * the window attach listener to remove. - */ - public void removeListener(WindowAttachListener listener) { - if (windowAttachListeners != null) { - windowAttachListeners.remove(listener); - if (windowAttachListeners.isEmpty()) { - windowAttachListeners = null; - } - } - } - - /** - * Removes the window detach listener. - * - * @param listener - * the window detach listener to remove. - */ - public void removeListener(WindowDetachListener listener) { - if (windowDetachListeners != null) { - windowDetachListeners.remove(listener); - if (windowDetachListeners.isEmpty()) { - windowDetachListeners = null; - } - } - } - - /** * Returns the URL user is redirected to on application close. If the URL is * <code>null</code>, the application is closed normally as defined by the * application running environment. @@ -1200,22 +1079,14 @@ public abstract class Application implements URIHandler, Object owner = null; if (event instanceof VariableOwner.ErrorEvent) { owner = ((VariableOwner.ErrorEvent) event).getVariableOwner(); - } else if (event instanceof URIHandler.ErrorEvent) { - owner = ((URIHandler.ErrorEvent) event).getURIHandler(); - } else if (event instanceof ParameterHandler.ErrorEvent) { - owner = ((ParameterHandler.ErrorEvent) event).getParameterHandler(); } else if (event instanceof ChangeVariablesErrorEvent) { owner = ((ChangeVariablesErrorEvent) event).getComponent(); } // Shows the error in AbstractComponent if (owner instanceof AbstractComponent) { - if (t instanceof ErrorMessage) { - ((AbstractComponent) owner).setComponentError((ErrorMessage) t); - } else { - ((AbstractComponent) owner) - .setComponentError(new SystemError(t)); - } + ((AbstractComponent) owner).setComponentError(AbstractErrorMessage + .getErrorMessageForException(t)); } // also print the error on console @@ -1280,6 +1151,43 @@ public abstract class Application implements URIHandler, } /** + * Gets the {@link ConverterFactory} used to locate a suitable + * {@link Converter} for fields in the application. + * + * See {@link #setConverterFactory(ConverterFactory)} for more details + * + * @return The converter factory used in the application + */ + public ConverterFactory getConverterFactory() { + return converterFactory; + } + + /** + * Sets the {@link ConverterFactory} used to locate a suitable + * {@link Converter} for fields in the application. + * <p> + * The {@link ConverterFactory} is used to find a suitable converter when + * binding data to a UI component and the data type does not match the UI + * component type, e.g. binding a Double to a TextField (which is based on a + * String). + * </p> + * <p> + * The {@link Converter} for an individual field can be overridden using + * {@link AbstractField#setConverter(Converter)} and for individual property + * ids in a {@link Table} using + * {@link Table#setConverter(Object, Converter)}. + * </p> + * <p> + * The converter factory must never be set to null. + * + * @param converterFactory + * The converter factory used in the application + */ + public void setConverterFactory(ConverterFactory converterFactory) { + this.converterFactory = converterFactory; + } + + /** * Contains the system messages used to notify the user about various * critical situations that can occur. * <p> @@ -1906,4 +1814,606 @@ public abstract class Application implements URIHandler, } } -}
\ No newline at end of file + + /** + * Gets a root for a request for which no root is already known. This method + * is called when the framework processes a request that does not originate + * from an existing root instance. This typically happens when a host page + * is requested. + * + * <p> + * Subclasses of Application may override this method to provide custom + * logic for choosing how to create a suitable root or for picking an + * already created root. If an existing root is picked, care should be taken + * to avoid keeping the same root open in multiple browser windows, as that + * will cause the states to go out of sync. + * </p> + * + * <p> + * If {@link BrowserDetails} are required to create a Root, the + * implementation can throw a {@link RootRequiresMoreInformationException} + * exception. In this case, the framework will instruct the browser to send + * the additional details, whereupon this method is invoked again with the + * browser details present in the wrapped request. Throwing the exception if + * the browser details are already available is not supported. + * </p> + * + * <p> + * The default implementation in {@link Application} creates a new instance + * of the Root class returned by {@link #getRootClassName(WrappedRequest)}, + * which in turn uses the {@value #ROOT_PARAMETER} parameter from web.xml. + * If {@link DeploymentConfiguration#getClassLoader()} for the request + * returns a {@link ClassLoader}, it is used for loading the Root class. + * Otherwise the {@link ClassLoader} used to load this class is used. + * </p> + * + * @param request + * the wrapped request for which a root is needed + * @return a root instance to use for the request + * @throws RootRequiresMoreInformationException + * may be thrown by an implementation to indicate that + * {@link BrowserDetails} are required to create a root + * + * @see #getRootClassName(WrappedRequest) + * @see Root + * @see RootRequiresMoreInformationException + * @see WrappedRequest#getBrowserDetails() + * + * @since 7.0 + */ + protected Root getRoot(WrappedRequest request) + throws RootRequiresMoreInformationException { + String rootClassName = getRootClassName(request); + try { + ClassLoader classLoader = request.getDeploymentConfiguration() + .getClassLoader(); + if (classLoader == null) { + classLoader = getClass().getClassLoader(); + } + Class<? extends Root> rootClass = Class.forName(rootClassName, + true, classLoader).asSubclass(Root.class); + try { + Root root = rootClass.newInstance(); + return root; + } catch (Exception e) { + throw new RuntimeException("Could not instantiate root class " + + rootClassName, e); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not load root class " + + rootClassName, e); + } + } + + /** + * Provides the name of the <code>Root</code> class that should be used for + * a request. The class must have an accessible no-args constructor. + * <p> + * The default implementation uses the {@value #ROOT_PARAMETER} parameter + * from web.xml. + * </p> + * <p> + * This method is mainly used by the default implementation of + * {@link #getRoot(WrappedRequest)}. If you override that method with your + * own functionality, the results of this method might not be used. + * </p> + * + * @param request + * the request for which a new root is required + * @return the name of the root class to use + * + * @since 7.0 + */ + protected String getRootClassName(WrappedRequest request) { + Object rootClassNameObj = properties.get(ROOT_PARAMETER); + if (rootClassNameObj instanceof String) { + return (String) rootClassNameObj; + } else { + throw new RuntimeException("No " + ROOT_PARAMETER + + " defined in web.xml"); + } + } + + /** + * Finds the theme to use for a specific root. If no specific theme is + * required, <code>null</code> is returned. + * + * TODO Tell what the default implementation does once it does something. + * + * @param root + * the root to get a theme for + * @return the name of the theme, or <code>null</code> if the default theme + * should be used + * + * @since 7.0 + */ + public String getThemeForRoot(Root root) { + Theme rootTheme = getAnnotationFor(root.getClass(), Theme.class); + if (rootTheme != null) { + return rootTheme.value(); + } else { + return null; + } + } + + /** + * Finds the widgetset to use for a specific root. If no specific widgetset + * is required, <code>null</code> is returned. + * + * TODO Tell what the default implementation does once it does something. + * + * @param root + * the root to get a widgetset for + * @return the name of the widgetset, or <code>null</code> if the default + * widgetset should be used + * + * @since 7.0 + */ + public String getWidgetsetForRoot(Root root) { + Widgetset rootWidgetset = getAnnotationFor(root.getClass(), + Widgetset.class); + if (rootWidgetset != null) { + return rootWidgetset.value(); + } else { + return null; + } + } + + /** + * Helper to get an annotation for a class. If the annotation is not present + * on the target class, it's superclasses and implemented interfaces are + * also searched for the annotation. + * + * @param type + * the target class from which the annotation should be found + * @param annotationType + * the annotation type to look for + * @return an annotation of the given type, or <code>null</code> if the + * annotation is not present on the class + */ + private static <T extends Annotation> T getAnnotationFor(Class<?> type, + Class<T> annotationType) { + // Find from the class hierarchy + Class<?> currentType = type; + while (currentType != Object.class) { + T annotation = currentType.getAnnotation(annotationType); + if (annotation != null) { + return annotation; + } else { + currentType = currentType.getSuperclass(); + } + } + + // Find from an implemented interface + for (Class<?> iface : type.getInterfaces()) { + T annotation = iface.getAnnotation(annotationType); + if (annotation != null) { + return annotation; + } + } + + return null; + } + + /** + * Handles a request by passing it to each registered {@link RequestHandler} + * in turn until one produces a response. This method is used for requests + * that have not been handled by any specific functionality in the terminal + * implementation (e.g. {@link AbstractApplicationServlet}). + * <p> + * The request handlers are invoked in the revere order in which they were + * added to the application until a response has been produced. This means + * that the most recently added handler is used first and the first request + * handler that was added to the application is invoked towards the end + * unless any previous handler has already produced a response. + * </p> + * + * @param request + * the wrapped request to get information from + * @param response + * the response to which data can be written + * @return returns <code>true</code> if a {@link RequestHandler} has + * produced a response and <code>false</code> if no response has + * been written. + * @throws IOException + * + * @see #addRequestHandler(RequestHandler) + * @see RequestHandler + * + * @since 7.0 + */ + public boolean handleRequest(WrappedRequest request, + WrappedResponse response) throws IOException { + // Use a copy to avoid ConcurrentModificationException + for (RequestHandler handler : new ArrayList<RequestHandler>( + requestHandlers)) { + if (handler.handleRequest(this, request, response)) { + return true; + } + } + // If not handled + return false; + } + + /** + * Adds a request handler to this application. Request handlers can be added + * to provide responses to requests that are not handled by the default + * functionality of the framework. + * <p> + * Handlers are called in reverse order of addition, so the most recently + * added handler will be called first. + * </p> + * + * @param handler + * the request handler to add + * + * @see #handleRequest(WrappedRequest, WrappedResponse) + * @see #removeRequestHandler(RequestHandler) + * + * @since 7.0 + */ + public void addRequestHandler(RequestHandler handler) { + requestHandlers.addFirst(handler); + } + + /** + * Removes a request handler from the application. + * + * @param handler + * the request handler to remove + * + * @since 7.0 + */ + public void removeRequestHandler(RequestHandler handler) { + requestHandlers.remove(handler); + } + + /** + * Gets the request handlers that are registered to the application. The + * iteration order of the returned collection is the same as the order in + * which the request handlers will be invoked when a request is handled. + * + * @return a collection of request handlers, with the iteration order + * according to the order they would be invoked + * + * @see #handleRequest(WrappedRequest, WrappedResponse) + * @see #addRequestHandler(RequestHandler) + * @see #removeRequestHandler(RequestHandler) + * + * @since 7.0 + */ + public Collection<RequestHandler> getRequestHandlers() { + return Collections.unmodifiableCollection(requestHandlers); + } + + /** + * Find an application resource with a given key. + * + * @param key + * The key of the resource + * @return The application resource corresponding to the provided key, or + * <code>null</code> if no resource is registered for the key + * + * @since 7.0 + */ + public ApplicationResource getResource(String key) { + return keyResourceMap.get(key); + } + + /** + * Thread local for keeping track of currently used application instance + * + * @since 7.0 + */ + private static final ThreadLocal<Application> currentApplication = new ThreadLocal<Application>(); + + private boolean rootPreserved = false; + + /** + * Gets the currently used application. The current application is + * automatically defined when processing requests to the server. In other + * cases, (e.g. from background threads), the current application is not + * automatically defined. + * + * @return the current application instance if available, otherwise + * <code>null</code> + * + * @see #setCurrentApplication(Application) + * + * @since 7.0 + */ + public static Application getCurrentApplication() { + return currentApplication.get(); + } + + /** + * Sets the thread local for the current application. 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. + * <p> + * The application developer can also use this method to define the current + * application outside the normal request handling, e.g. when initiating + * custom background threads. + * </p> + * + * @param application + * + * @see #getCurrentApplication() + * @see ThreadLocal + * + * @since 7.0 + */ + public static void setCurrentApplication(Application application) { + currentApplication.set(application); + } + + /** + * Check whether this application is in production mode. If an application + * is in production mode, certain debugging facilities are not available. + * + * @return the status of the production mode flag + * + * @since 7.0 + */ + public boolean isProductionMode() { + return productionMode; + } + + /** + * Finds the {@link Root} to which a particular request belongs. If the + * request originates from an existing Root, that root is returned. In other + * cases, the method attempts to create and initialize a new root and might + * throw a {@link RootRequiresMoreInformationException} if all required + * information is not available. + * <p> + * Please note that this method can also return a newly created + * <code>Root</code> which has not yet been initialized. You can use + * {@link #isRootInitPending(int)} with the root's id ( + * {@link Root#getRootId()} to check whether the initialization is still + * pending. + * </p> + * + * @param request + * the request for which a root is desired + * @return a root belonging to the request + * @throws RootRequiresMoreInformationException + * if no existing root could be found and creating a new root + * requires additional information from the browser + * + * @see #getRoot(WrappedRequest) + * @see RootRequiresMoreInformationException + * + * @since 7.0 + */ + public Root getRootForRequest(WrappedRequest request) + throws RootRequiresMoreInformationException { + Root root = Root.getCurrentRoot(); + if (root != null) { + return root; + } + Integer rootId = getRootId(request); + + synchronized (this) { + BrowserDetails browserDetails = request.getBrowserDetails(); + boolean hasBrowserDetails = browserDetails != null + && browserDetails.getUriFragment() != null; + + root = roots.get(rootId); + + if (root == null && isRootPreserved()) { + // Check for a known root + if (!retainOnRefreshRoots.isEmpty()) { + + Integer retainedRootId; + if (!hasBrowserDetails) { + throw new RootRequiresMoreInformationException(); + } else { + String windowName = browserDetails.getWindowName(); + retainedRootId = retainOnRefreshRoots.get(windowName); + } + + if (retainedRootId != null) { + rootId = retainedRootId; + root = roots.get(rootId); + } + } + } + + if (root == null) { + // Throws exception if root can not yet be created + root = getRoot(request); + + // Initialize some fields for a newly created root + if (root.getApplication() == null) { + root.setApplication(this); + } + if (root.getRootId() < 0) { + + if (rootId == null) { + // Get the next id if none defined + rootId = Integer.valueOf(nextRootId++); + } + root.setRootId(rootId.intValue()); + roots.put(rootId, root); + } + } + + // Set thread local here so it is available in init + Root.setCurrentRoot(root); + + if (!initedRoots.contains(rootId)) { + boolean initRequiresBrowserDetails = isRootPreserved() + || !root.getClass() + .isAnnotationPresent(EagerInit.class); + if (!initRequiresBrowserDetails || hasBrowserDetails) { + root.doInit(request); + + // Remember that this root has been initialized + initedRoots.add(rootId); + + // init() might turn on preserve so do this afterwards + if (isRootPreserved()) { + // Remember this root + String windowName = request.getBrowserDetails() + .getWindowName(); + retainOnRefreshRoots.put(windowName, rootId); + } + } + } + } // end synchronized block + + return root; + } + + /** + * Internal helper to finds the root id for a request. + * + * @param request + * the request to get the root id for + * @return a root id, or <code>null</code> if no root id is defined + * + * @since 7.0 + */ + private static Integer getRootId(WrappedRequest request) { + if (request instanceof CombinedRequest) { + // Combined requests has the rootid parameter in the second request + CombinedRequest combinedRequest = (CombinedRequest) request; + request = combinedRequest.getSecondRequest(); + } + String rootIdString = request + .getParameter(ApplicationConnection.ROOT_ID_PARAMETER); + Integer rootId = rootIdString == null ? null + : new Integer(rootIdString); + return rootId; + } + + /** + * Sets whether the same Root state should be reused if the framework can + * detect that the application is opened in a browser window where it has + * previously been open. The framework attempts to discover this by checking + * the value of window.name in the browser. + * <p> + * NOTE that you should avoid turning this feature on/off on-the-fly when + * the UI is already shown, as it might not be retained as intended. + * </p> + * + * @param rootPreserved + * <code>true</code>if the same Root instance should be reused + * e.g. when the browser window is refreshed. + */ + public void setRootPreserved(boolean rootPreserved) { + this.rootPreserved = rootPreserved; + if (!rootPreserved) { + retainOnRefreshRoots.clear(); + } + } + + /** + * Checks whether the same Root state should be reused if the framework can + * detect that the application is opened in a browser window where it has + * previously been open. The framework attempts to discover this by checking + * the value of window.name in the browser. + * + * @return <code>true</code>if the same Root instance should be reused e.g. + * when the browser window is refreshed. + */ + public boolean isRootPreserved() { + return rootPreserved; + } + + /** + * Checks whether there's a pending initialization for the root with the + * given id. + * + * @param rootId + * root id to check for + * @return <code>true</code> of the initialization is pending, + * <code>false</code> if the root id is not registered or if the + * root has already been initialized + * + * @see #getRootForRequest(WrappedRequest) + */ + public boolean isRootInitPending(int rootId) { + return !initedRoots.contains(Integer.valueOf(rootId)); + } + + /** + * Gets all the roots of this application. This includes roots that have + * been requested but not yet initialized. Please note, that roots are not + * automatically removed e.g. if the browser window is closed and that there + * is no way to manually remove a root. Inactive roots will thus not be + * released for GC until the entire application is released when the session + * has timed out (unless there are dangling references). Improved support + * for releasing unused roots is planned for an upcoming alpha release of + * Vaadin 7. + * + * @return a collection of roots belonging to this application + * + * @since 7.0 + */ + public Collection<Root> getRoots() { + return Collections.unmodifiableCollection(roots.values()); + } + + private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<String, ClientConnector>(); + + private int connectorIdSequence = 0; + + /** + * Generate an id for the given Connector. Connectors must not call this + * method more than once, the first time they need an id. + * + * @param connector + * A connector that has not yet been assigned an id. + * @return A new id for the connector + */ + public String createConnectorId(ClientConnector connector) { + String connectorId = String.valueOf(connectorIdSequence++); + Connector oldReference = connectorIdToConnector.put(connectorId, + connector); + if (oldReference != null) { + throw new RuntimeException( + "An error occured while generating connector ids. A connector with id " + + connectorId + " was already found!"); + } + return connectorId; + } + + /** + * Gets a connector by its id. + * + * @param connectorId + * The connector id to look for + * @return The connector with the given id or null if no connector has the + * given id + */ + public ClientConnector getConnector(String connectorId) { + return connectorIdToConnector.get(connectorId); + } + + /** + * Cleans the connector map from all connectors that are no longer attached + * to the application. This should only be called by the framework. + */ + public void cleanConnectorMap() { + // remove detached components from paintableIdMap so they + // can be GC'ed + Iterator<String> iterator = connectorIdToConnector.keySet().iterator(); + + while (iterator.hasNext()) { + String connectorId = iterator.next(); + Connector connector = connectorIdToConnector.get(connectorId); + if (connector instanceof Component) { + Component component = (Component) connector; + if (component.getApplication() != this) { + // If component is no longer part of this application, + // remove it from the map. If it is re-attached to the + // application at some point it will be re-added to this + // collection when sent to the client. + iterator.remove(); + } + } + } + + } +} diff --git a/src/com/vaadin/RootRequiresMoreInformationException.java b/src/com/vaadin/RootRequiresMoreInformationException.java new file mode 100644 index 0000000000..ed0fa41437 --- /dev/null +++ b/src/com/vaadin/RootRequiresMoreInformationException.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin; + +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedRequest.BrowserDetails; + +/** + * Exception that is thrown to indicate that creating or initializing the root + * requires information detailed from the web browser ({@link BrowserDetails}) + * to be present. + * + * This exception may not be thrown if that information is already present in + * the current WrappedRequest. + * + * @see Application#getRoot(WrappedRequest) + * @see WrappedRequest#getBrowserDetails() + * + * @since 7.0 + */ +public class RootRequiresMoreInformationException extends Exception { + // Nothing of interest here +} diff --git a/src/com/vaadin/annotations/EagerInit.java b/src/com/vaadin/annotations/EagerInit.java new file mode 100644 index 0000000000..c7c2702d2a --- /dev/null +++ b/src/com/vaadin/annotations/EagerInit.java @@ -0,0 +1,30 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.ui.Root; + +/** + * Indicates that the init method in a Root class can be called before full + * browser details ({@link WrappedRequest#getBrowserDetails()}) are available. + * This will make the UI appear more quickly, as ensuring the availability of + * this information typically requires an additional round trip to the client. + * + * @see Root#init(com.vaadin.terminal.WrappedRequest) + * @see WrappedRequest#getBrowserDetails() + * + * @since 7.0 + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface EagerInit { + // No values +} diff --git a/src/com/vaadin/annotations/Theme.java b/src/com/vaadin/annotations/Theme.java new file mode 100644 index 0000000000..7c62b07741 --- /dev/null +++ b/src/com/vaadin/annotations/Theme.java @@ -0,0 +1,24 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.vaadin.ui.Root; + +/** + * Defines a specific theme for a {@link Root}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Theme { + /** + * @return simple name of the theme + */ + public String value(); +} diff --git a/src/com/vaadin/annotations/Widgetset.java b/src/com/vaadin/annotations/Widgetset.java new file mode 100644 index 0000000000..99113f73f9 --- /dev/null +++ b/src/com/vaadin/annotations/Widgetset.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.vaadin.ui.Root; + +/** + * Defines a specific theme for a {@link Root}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Widgetset { + /** + * @return name of the widgetset + */ + public String value(); + +} diff --git a/src/com/vaadin/data/Buffered.java b/src/com/vaadin/data/Buffered.java index 28167f71df..1387cb965b 100644 --- a/src/com/vaadin/data/Buffered.java +++ b/src/com/vaadin/data/Buffered.java @@ -7,10 +7,6 @@ package com.vaadin.data; import java.io.Serializable; import com.vaadin.data.Validator.InvalidValueException; -import com.vaadin.terminal.ErrorMessage; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.SystemError; /** * <p> @@ -77,7 +73,11 @@ public interface Buffered extends Serializable { * * @return <code>true</code> if the object is in write-through mode, * <code>false</code> if it's not. + * @deprecated Use {@link #setBuffered(boolean)} instead. Note that + * setReadThrough(true), setWriteThrough(true) equals + * setBuffered(false) */ + @Deprecated public boolean isWriteThrough(); /** @@ -95,7 +95,11 @@ public interface Buffered extends Serializable { * If the implicit commit operation fails because of a * validation error. * + * @deprecated Use {@link #setBuffered(boolean)} instead. Note that + * setReadThrough(true), setWriteThrough(true) equals + * setBuffered(false) */ + @Deprecated public void setWriteThrough(boolean writeThrough) throws SourceException, InvalidValueException; @@ -112,7 +116,11 @@ public interface Buffered extends Serializable { * * @return <code>true</code> if the object is in read-through mode, * <code>false</code> if it's not. + * @deprecated Use {@link #isBuffered(boolean)} instead. Note that + * setReadThrough(true), setWriteThrough(true) equals + * setBuffered(false) */ + @Deprecated public boolean isReadThrough(); /** @@ -127,10 +135,52 @@ public interface Buffered extends Serializable { * @throws SourceException * If the operation fails because of an exception is thrown by * the data source. The cause is included in the exception. + * @deprecated Use {@link #setBuffered(boolean)} instead. Note that + * setReadThrough(true), setWriteThrough(true) equals + * setBuffered(false) */ + @Deprecated public void setReadThrough(boolean readThrough) throws SourceException; /** + * Sets the object's buffered mode to the specified status. + * <p> + * When the object is in buffered mode, an internal buffer will be used to + * store changes until {@link #commit()} is called. Calling + * {@link #discard()} will revert the internal buffer to the value of the + * data source. + * </p> + * <p> + * This is an easier way to use {@link #setReadThrough(boolean)} and + * {@link #setWriteThrough(boolean)} and not as error prone. Changing + * buffered mode will change both the read through and write through state + * of the object. + * </p> + * <p> + * Mixing calls to {@link #setBuffered(boolean)}/{@link #isBuffered()} and + * {@link #setReadThrough(boolean)}/{@link #isReadThrough()} or + * {@link #setWriteThrough(boolean)}/{@link #isWriteThrough()} is generally + * a bad idea. + * </p> + * + * @param buffered + * true if buffered mode should be turned on, false otherwise + * @since 7.0 + */ + public void setBuffered(boolean buffered); + + /** + * Checks the buffered mode of this Object. + * <p> + * This method only returns true if both read and write buffering is used. + * </p> + * + * @return true if buffered mode is on, false otherwise + * @since 7.0 + */ + public boolean isBuffered(); + + /** * Tests if the value stored in the object has been modified since it was * last updated from the data source. * @@ -151,7 +201,7 @@ public interface Buffered extends Serializable { */ @SuppressWarnings("serial") public class SourceException extends RuntimeException implements - ErrorMessage, Serializable { + Serializable { /** Source class implementing the buffered interface */ private final Buffered source; @@ -198,11 +248,7 @@ public interface Buffered extends Serializable { /** * Gets the cause of the exception. * - * @return The cause for the exception. - * @throws MoreThanOneCauseException - * if there is more than one cause for the exception. This - * is possible if the commit operation triggers more than - * one error at the same time. + * @return The (first) cause for the exception, null if no cause. */ @Override public final Throwable getCause() { @@ -230,86 +276,5 @@ public interface Buffered extends Serializable { return source; } - /** - * Gets the error level of this buffered source exception. The level of - * the exception is maximum error level of all the contained causes. - * <p> - * The causes that do not specify error level default to - * <code>ERROR</code> level. Also source exception without any causes - * are of level <code>ERROR</code>. - * </p> - * - * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() - */ - public int getErrorLevel() { - - int level = Integer.MIN_VALUE; - - for (int i = 0; i < causes.length; i++) { - final int causeLevel = (causes[i] instanceof ErrorMessage) ? ((ErrorMessage) causes[i]) - .getErrorLevel() : ErrorMessage.ERROR; - if (causeLevel > level) { - level = causeLevel; - } - } - - return level == Integer.MIN_VALUE ? ErrorMessage.ERROR : level; - } - - /* Documented in super interface */ - public void paint(PaintTarget target) throws PaintException { - target.startTag("error"); - final int level = getErrorLevel(); - if (level > 0 && level <= ErrorMessage.INFORMATION) { - target.addAttribute("level", "info"); - } else if (level <= ErrorMessage.WARNING) { - target.addAttribute("level", "warning"); - } else if (level <= ErrorMessage.ERROR) { - target.addAttribute("level", "error"); - } else if (level <= ErrorMessage.CRITICAL) { - target.addAttribute("level", "critical"); - } else { - target.addAttribute("level", "system"); - } - - // Paint all the exceptions - for (int i = 0; i < causes.length; i++) { - if (causes[i] instanceof ErrorMessage) { - ((ErrorMessage) causes[i]).paint(target); - } else { - new SystemError(causes[i]).paint(target); - } - } - - target.endTag("error"); - - } - - /* Documented in super interface */ - public void addListener(RepaintRequestListener listener) { - } - - /* Documented in super interface */ - public void removeListener(RepaintRequestListener listener) { - } - - /* Documented in super interface */ - public void requestRepaint() { - } - - /* Documented in super interface */ - public void requestRepaintRequests() { - } - - public String getDebugId() { - // TODO Auto-generated method stub - return null; - } - - public void setDebugId(String id) { - throw new UnsupportedOperationException( - "Setting testing id for this Paintable is not implemented"); - } - } } diff --git a/src/com/vaadin/ui/treetable/Collapsible.java b/src/com/vaadin/data/Collapsible.java index bec0ba9ae9..06c96b7ea7 100644 --- a/src/com/vaadin/ui/treetable/Collapsible.java +++ b/src/com/vaadin/data/Collapsible.java @@ -1,15 +1,14 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.ui.treetable; +package com.vaadin.data; -import com.vaadin.data.Container; import com.vaadin.data.Container.Hierarchical; import com.vaadin.data.Container.Ordered; -import com.vaadin.data.Item; /** - * Container needed by large lazy loading hierarchies displayed in TreeTable. + * Container needed by large lazy loading hierarchies displayed e.g. in + * TreeTable. * <p> * Container of this type gets notified when a subtree is opened/closed in a * component displaying its content. This allows container to lazy load subtrees diff --git a/src/com/vaadin/data/Container.java b/src/com/vaadin/data/Container.java index 7f64dc6efa..f722e07741 100644 --- a/src/com/vaadin/data/Container.java +++ b/src/com/vaadin/data/Container.java @@ -121,7 +121,7 @@ public interface Container extends Serializable { * ID of the Property to retrieve * @return Property with the given ID or <code>null</code> */ - public Property getContainerProperty(Object itemId, Object propertyId); + public Property<?> getContainerProperty(Object itemId, Object propertyId); /** * Gets the data type of all Properties identified by the given Property ID. @@ -1101,4 +1101,4 @@ public interface Container extends Serializable { */ public void removeListener(Container.PropertySetChangeListener listener); } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/data/Item.java b/src/com/vaadin/data/Item.java index 3a884a99ab..98b95aecff 100644 --- a/src/com/vaadin/data/Item.java +++ b/src/com/vaadin/data/Item.java @@ -30,7 +30,7 @@ public interface Item extends Serializable { * identifier of the Property to get * @return the Property with the given ID or <code>null</code> */ - public Property getItemProperty(Object id); + public Property<?> getItemProperty(Object id); /** * Gets the collection of IDs of all Properties stored in the Item. diff --git a/src/com/vaadin/data/Property.java b/src/com/vaadin/data/Property.java index 70d57c3aee..9fab642381 100644 --- a/src/com/vaadin/data/Property.java +++ b/src/com/vaadin/data/Property.java @@ -30,12 +30,15 @@ import java.io.Serializable; * needs to be changed through the implementing class. * </p> * + * @param T + * type of values of the property + * * @author Vaadin Ltd * @version * @VERSION@ * @since 3.0 */ -public interface Property extends Serializable { +public interface Property<T> extends Serializable { /** * Gets the value stored in the Property. The returned object is compatible @@ -43,7 +46,7 @@ public interface Property extends Serializable { * * @return the value stored in the Property */ - public Object getValue(); + public T getValue(); /** * Sets the value of the Property. @@ -52,37 +55,18 @@ public interface Property extends Serializable { * missing, one should declare the Property to be in read-only mode and * throw <code>Property.ReadOnlyException</code> in this function. * </p> - * Note : It is not required, but highly recommended to support setting the - * value also as a <code>String</code> in addition to the native type of the - * Property (as given by the <code>getType</code> method). If the - * <code>String</code> conversion fails or is unsupported, the method should - * throw <code>Property.ConversionException</code>. The string conversion - * should at least understand the format returned by the - * <code>toString</code> method of the Property. + * + * Note : Since Vaadin 7.0, setting the value of a non-String property as a + * String is no longer supported. * * @param newValue * New value of the Property. This should be assignable to the - * type returned by getType, but also String type should be - * supported + * type returned by getType * * @throws Property.ReadOnlyException * if the object is in read-only mode - * @throws Property.ConversionException - * if newValue can't be converted into the Property's native - * type directly or through String */ - public void setValue(Object newValue) throws Property.ReadOnlyException, - Property.ConversionException; - - /** - * Returns the value of the Property in human readable textual format. The - * return value should be assignable to the <code>setValue</code> method if - * the Property is not in read-only mode. - * - * @return <code>String</code> representation of the value stored in the - * Property - */ - public String toString(); + public void setValue(Object newValue) throws Property.ReadOnlyException; /** * Returns the type of the Property. The methods <code>getValue</code> and @@ -93,7 +77,7 @@ public interface Property extends Serializable { * * @return type of the Property */ - public Class<?> getType(); + public Class<? extends T> getType(); /** * Tests if the Property is in read-only mode. In read-only mode calls to @@ -118,90 +102,94 @@ public interface Property extends Serializable { public void setReadOnly(boolean newStatus); /** - * <code>Exception</code> object that signals that a requested Property - * modification failed because it's in read-only mode. + * A Property that is capable of handle a transaction that can end in commit + * or rollback. * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 3.0 + * Note that this does not refer to e.g. database transactions but rather + * two-phase commit that allows resetting old field values on a form etc. if + * the commit of one of the properties fails after others have already been + * committed. If + * + * @param <T> + * The type of the property + * @author Vaadin Ltd + * @version @version@ + * @since 7.0 */ - @SuppressWarnings("serial") - public class ReadOnlyException extends RuntimeException { + public interface Transactional<T> extends Property<T> { /** - * Constructs a new <code>ReadOnlyException</code> without a detail - * message. + * Starts a transaction. + * + * <p> + * If the value is set during a transaction the value must not replace + * the original value until {@link #commit()} is called. Still, + * {@link #getValue()} must return the current value set in the + * transaction. Calling {@link #rollback()} while in a transaction must + * rollback the value to what it was before the transaction started. + * </p> + * <p> + * {@link ValueChangeEvent}s must not be emitted for internal value + * changes during a transaction. If the value changes as a result of + * {@link #commit()}, a {@link ValueChangeEvent} should be emitted. + * </p> */ - public ReadOnlyException() { - } + public void startTransaction(); /** - * Constructs a new <code>ReadOnlyException</code> with the specified - * detail message. - * - * @param msg - * the detail message + * Commits and ends the transaction that is in progress. + * <p> + * If the value is changed as a result of this operation, a + * {@link ValueChangeEvent} is emitted if such are supported. + * <p> + * This method has no effect if there is no transaction is in progress. + * <p> + * This method must never throw an exception. */ - public ReadOnlyException(String msg) { - super(msg); - } + public void commit(); + + /** + * Aborts and rolls back the transaction that is in progress. + * <p> + * The value is reset to the value before the transaction started. No + * {@link ValueChangeEvent} is emitted as a result of this. + * <p> + * This method has no effect if there is no transaction is in progress. + * <p> + * This method must never throw an exception. + */ + public void rollback(); } /** - * An exception that signals that the value passed to the - * <code>setValue</code> method couldn't be converted to the native type of - * the Property. + * <code>Exception</code> object that signals that a requested Property + * modification failed because it's in read-only mode. * - * @author Vaadin Ltd + * @author Vaadin Ltd. * @version * @VERSION@ * @since 3.0 */ @SuppressWarnings("serial") - public class ConversionException extends RuntimeException { + public class ReadOnlyException extends RuntimeException { /** - * Constructs a new <code>ConversionException</code> without a detail + * Constructs a new <code>ReadOnlyException</code> without a detail * message. */ - public ConversionException() { + public ReadOnlyException() { } /** - * Constructs a new <code>ConversionException</code> with the specified + * Constructs a new <code>ReadOnlyException</code> with the specified * detail message. * * @param msg * the detail message */ - public ConversionException(String msg) { + public ReadOnlyException(String msg) { super(msg); } - - /** - * Constructs a new <code>ConversionException</code> from another - * exception. - * - * @param cause - * The cause of the the conversion failure - */ - public ConversionException(Throwable cause) { - super(cause); - } - - /** - * Constructs a new <code>ConversionException</code> with the specified - * detail message and cause. - * - * @param message - * the detail message - * @param cause - * The cause of the the conversion failure - */ - public ConversionException(String message, Throwable cause) { - super(message, cause); - } } /** diff --git a/src/com/vaadin/data/Validator.java b/src/com/vaadin/data/Validator.java index fc4cdf5b42..768a02babe 100644 --- a/src/com/vaadin/data/Validator.java +++ b/src/com/vaadin/data/Validator.java @@ -6,9 +6,6 @@ package com.vaadin.data; import java.io.Serializable; -import com.vaadin.terminal.ErrorMessage; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; /** @@ -20,15 +17,20 @@ import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; * value. * </p> * <p> - * {@link #isValid(Object)} and {@link #validate(Object)} can be used to check - * if a value is valid. {@link #isValid(Object)} and {@link #validate(Object)} - * must use the same validation logic so that iff {@link #isValid(Object)} - * returns false, {@link #validate(Object)} throws an - * {@link InvalidValueException}. + * {@link #validate(Object)} can be used to check if a value is valid. An + * {@link InvalidValueException} with an appropriate validation error message is + * thrown if the value is not valid. * </p> * <p> * Validators must not have any side effects. * </p> + * <p> + * Since Vaadin 7, the method isValid(Object) does not exist in the interface - + * {@link #validate(Object)} should be used instead, and the exception caught + * where applicable. Concrete classes implementing {@link Validator} can still + * internally implement and use isValid(Object) for convenience or to ease + * migration from earlier Vaadin versions. + * </p> * * @author Vaadin Ltd. * @version @@ -50,18 +52,6 @@ public interface Validator extends Serializable { public void validate(Object value) throws Validator.InvalidValueException; /** - * Tests if the given value is valid. This method must be symmetric with - * {@link #validate(Object)} so that {@link #validate(Object)} throws an - * error iff this method returns false. - * - * @param value - * the value to check - * @return <code>true</code> if the value is valid, <code>false</code> - * otherwise. - */ - public boolean isValid(Object value); - - /** * Exception that is thrown by a {@link Validator} when a value is invalid. * * <p> @@ -76,8 +66,7 @@ public interface Validator extends Serializable { * @since 3.0 */ @SuppressWarnings("serial") - public class InvalidValueException extends RuntimeException implements - ErrorMessage { + public class InvalidValueException extends RuntimeException { /** * Array of one or more validation errors that are causing this @@ -141,104 +130,16 @@ public interface Validator extends Serializable { return true; } - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() - */ - public final int getErrorLevel() { - return ErrorMessage.ERROR; - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.Paintable#paint(com.vaadin.terminal.PaintTarget) - */ - public void paint(PaintTarget target) throws PaintException { - target.startTag("error"); - target.addAttribute("level", "error"); - - // Error message - final String message = getHtmlMessage(); - if (message != null) { - target.addText(message); - } - - // Paint all the causes - for (int i = 0; i < causes.length; i++) { - causes[i].paint(target); - } - - target.endTag("error"); - } - /** * Returns the message of the error in HTML. * * Note that this API may change in future versions. */ - protected String getHtmlMessage() { + public String getHtmlMessage() { return AbstractApplicationServlet .safeEscapeForHtml(getLocalizedMessage()); } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.ErrorMessage#addListener(com.vaadin.terminal. - * Paintable.RepaintRequestListener) - */ - public void addListener(RepaintRequestListener listener) { - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.ErrorMessage#removeListener(com.vaadin.terminal - * .Paintable.RepaintRequestListener) - */ - public void removeListener(RepaintRequestListener listener) { - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.ErrorMessage#requestRepaint() - */ - public void requestRepaint() { - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Paintable#requestRepaintRequests() - */ - public void requestRepaintRequests() { - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Paintable#getDebugId() - */ - public String getDebugId() { - return null; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Paintable#setDebugId(java.lang.String) - */ - public void setDebugId(String id) { - throw new UnsupportedOperationException( - "InvalidValueException cannot have a debug id"); - } - /** * Returns the {@code InvalidValueExceptions} that caused this * exception. diff --git a/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java new file mode 100644 index 0000000000..b8efa5b1e4 --- /dev/null +++ b/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java @@ -0,0 +1,157 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.fieldgroup; + +import java.lang.reflect.Method; + +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.validator.BeanValidator; +import com.vaadin.ui.Field; + +public class BeanFieldGroup<T> extends FieldGroup { + + private Class<T> beanType; + + private static Boolean beanValidationImplementationAvailable = null; + + public BeanFieldGroup(Class<T> beanType) { + this.beanType = beanType; + } + + @Override + protected Class<?> getPropertyType(Object propertyId) { + if (getItemDataSource() != null) { + return super.getPropertyType(propertyId); + } else { + // Data source not set so we need to figure out the type manually + /* + * toString should never really be needed as propertyId should be of + * form "fieldName" or "fieldName.subField[.subField2]" but the + * method declaration comes from parent. + */ + java.lang.reflect.Field f; + try { + f = getField(beanType, propertyId.toString()); + return f.getType(); + } catch (SecurityException e) { + throw new BindException("Cannot determine type of propertyId '" + + propertyId + "'.", e); + } catch (NoSuchFieldException e) { + throw new BindException("Cannot determine type of propertyId '" + + propertyId + "'. The propertyId was not found in " + + beanType.getName(), e); + } + } + } + + private static java.lang.reflect.Field getField(Class<?> cls, + String propertyId) throws SecurityException, NoSuchFieldException { + if (propertyId.contains(".")) { + String[] parts = propertyId.split("\\.", 2); + // Get the type of the field in the "cls" class + java.lang.reflect.Field field1 = getField(cls, parts[0]); + // Find the rest from the sub type + return getField(field1.getType(), parts[1]); + } else { + try { + // Try to find the field directly in the given class + java.lang.reflect.Field field1 = cls + .getDeclaredField(propertyId); + return field1; + } catch (NoSuchFieldError e) { + // Try super classes until we reach Object + Class<?> superClass = cls.getSuperclass(); + if (superClass != Object.class) { + return getField(superClass, propertyId); + } else { + throw e; + } + } + } + } + + /** + * Helper method for setting the data source directly using a bean. This + * method wraps the bean in a {@link BeanItem} and calls + * {@link #setItemDataSource(Item)}. + * + * @param bean + * The bean to use as data source. + */ + public void setItemDataSource(T bean) { + setItemDataSource(new BeanItem(bean)); + } + + @Override + public void setItemDataSource(Item item) { + if (!(item instanceof BeanItem)) { + throw new RuntimeException(getClass().getSimpleName() + + " only supports BeanItems as item data source"); + } + super.setItemDataSource(item); + } + + @Override + public BeanItem<T> getItemDataSource() { + return (BeanItem<T>) super.getItemDataSource(); + } + + @Override + public void bind(Field field, Object propertyId) { + if (getItemDataSource() != null) { + // The data source is set so the property must be found in the item. + // If it is not we try to add it. + try { + getItemProperty(propertyId); + } catch (BindException e) { + // Not found, try to add a nested property; + // BeanItem property ids are always strings so this is safe + getItemDataSource().addNestedProperty((String) propertyId); + } + } + + super.bind(field, propertyId); + } + + @Override + protected void configureField(Field<?> field) { + super.configureField(field); + // Add Bean validators if there are annotations + if (isBeanValidationImplementationAvailable()) { + BeanValidator validator = new BeanValidator(beanType, + getPropertyId(field).toString()); + field.addValidator(validator); + if (field.getLocale() != null) { + validator.setLocale(field.getLocale()); + } + } + } + + /** + * Checks whether a bean validation implementation (e.g. Hibernate Validator + * or Apache Bean Validation) is available. + * + * TODO move this method to some more generic location + * + * @return true if a JSR-303 bean validation implementation is available + */ + protected static boolean isBeanValidationImplementationAvailable() { + if (beanValidationImplementationAvailable != null) { + return beanValidationImplementationAvailable; + } + try { + Class<?> validationClass = Class + .forName("javax.validation.Validation"); + Method buildFactoryMethod = validationClass + .getMethod("buildDefaultValidatorFactory"); + Object factory = buildFactoryMethod.invoke(null); + beanValidationImplementationAvailable = (factory != null); + } catch (Exception e) { + // no bean validation implementation available + beanValidationImplementationAvailable = false; + } + return beanValidationImplementationAvailable; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/data/fieldgroup/Caption.java b/src/com/vaadin/data/fieldgroup/Caption.java new file mode 100644 index 0000000000..b990b720cd --- /dev/null +++ b/src/com/vaadin/data/fieldgroup/Caption.java @@ -0,0 +1,15 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.fieldgroup; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Caption { + String value(); +} diff --git a/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java b/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java new file mode 100644 index 0000000000..569f643998 --- /dev/null +++ b/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java @@ -0,0 +1,156 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.fieldgroup; + +import java.util.EnumSet; + +import com.vaadin.data.Item; +import com.vaadin.data.fieldgroup.FieldGroup.BindException; +import com.vaadin.ui.AbstractSelect; +import com.vaadin.ui.AbstractTextField; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Field; +import com.vaadin.ui.ListSelect; +import com.vaadin.ui.NativeSelect; +import com.vaadin.ui.OptionGroup; +import com.vaadin.ui.RichTextArea; +import com.vaadin.ui.Table; +import com.vaadin.ui.TextField; + +public class DefaultFieldGroupFieldFactory implements FieldGroupFieldFactory { + + public static final Object CAPTION_PROPERTY_ID = "Caption"; + + public <T extends Field> T createField(Class<?> type, Class<T> fieldType) { + if (Enum.class.isAssignableFrom(type)) { + return createEnumField(type, fieldType); + } else if (Boolean.class.isAssignableFrom(type) + || boolean.class.isAssignableFrom(type)) { + return createBooleanField(fieldType); + } + if (AbstractTextField.class.isAssignableFrom(fieldType)) { + return fieldType.cast(createAbstractTextField(fieldType + .asSubclass(AbstractTextField.class))); + } else if (fieldType == RichTextArea.class) { + return fieldType.cast(createRichTextArea()); + } + return createDefaultField(type, fieldType); + } + + protected RichTextArea createRichTextArea() { + RichTextArea rta = new RichTextArea(); + rta.setImmediate(true); + + return rta; + } + + private <T extends Field> T createEnumField(Class<?> type, + Class<T> fieldType) { + if (AbstractSelect.class.isAssignableFrom(fieldType)) { + AbstractSelect s = createCompatibleSelect((Class<? extends AbstractSelect>) fieldType); + populateWithEnumData(s, (Class<? extends Enum>) type); + return (T) s; + } + + return null; + } + + protected AbstractSelect createCompatibleSelect( + Class<? extends AbstractSelect> fieldType) { + AbstractSelect select; + if (fieldType.isAssignableFrom(ListSelect.class)) { + select = new ListSelect(); + select.setMultiSelect(false); + } else if (fieldType.isAssignableFrom(NativeSelect.class)) { + select = new NativeSelect(); + } else if (fieldType.isAssignableFrom(OptionGroup.class)) { + select = new OptionGroup(); + select.setMultiSelect(false); + } else if (fieldType.isAssignableFrom(Table.class)) { + Table t = new Table(); + t.setSelectable(true); + select = t; + } else { + select = new ComboBox(null); + } + select.setImmediate(true); + select.setNullSelectionAllowed(false); + + return select; + } + + protected <T extends Field> T createBooleanField(Class<T> fieldType) { + if (fieldType.isAssignableFrom(CheckBox.class)) { + CheckBox cb = new CheckBox(null); + cb.setImmediate(true); + return (T) cb; + } else if (AbstractTextField.class.isAssignableFrom(fieldType)) { + return (T) createAbstractTextField((Class<? extends AbstractTextField>) fieldType); + } + + return null; + } + + protected <T extends AbstractTextField> T createAbstractTextField( + Class<T> fieldType) { + if (fieldType == AbstractTextField.class) { + fieldType = (Class<T>) TextField.class; + } + try { + T field = fieldType.newInstance(); + field.setImmediate(true); + return field; + } catch (Exception e) { + throw new BindException("Could not create a field of type " + + fieldType, e); + } + } + + /** + * Fallback when no specific field has been created. Typically returns a + * TextField. + * + * @param <T> + * The type of field to create + * @param type + * The type of data that should be edited + * @param fieldType + * The type of field to create + * @return A field capable of editing the data or null if no field could be + * created + */ + protected <T extends Field> T createDefaultField(Class<?> type, + Class<T> fieldType) { + if (fieldType.isAssignableFrom(TextField.class)) { + return fieldType.cast(createAbstractTextField(TextField.class)); + } + return null; + } + + /** + * Populates the given select with all the enums in the given {@link Enum} + * class. Uses {@link Enum}.toString() for caption. + * + * @param select + * The select to populate + * @param enumClass + * The Enum class to use + */ + protected void populateWithEnumData(AbstractSelect select, + Class<? extends Enum> enumClass) { + select.removeAllItems(); + for (Object p : select.getContainerPropertyIds()) { + select.removeContainerProperty(p); + } + select.addContainerProperty(CAPTION_PROPERTY_ID, String.class, ""); + select.setItemCaptionPropertyId(CAPTION_PROPERTY_ID); + @SuppressWarnings("unchecked") + EnumSet<?> enumSet = EnumSet.allOf(enumClass); + for (Object r : enumSet) { + Item newItem = select.addItem(r); + newItem.getItemProperty(CAPTION_PROPERTY_ID).setValue(r.toString()); + } + } +} diff --git a/src/com/vaadin/data/fieldgroup/FieldGroup.java b/src/com/vaadin/data/fieldgroup/FieldGroup.java new file mode 100644 index 0000000000..3df19f5bc9 --- /dev/null +++ b/src/com/vaadin/data/fieldgroup/FieldGroup.java @@ -0,0 +1,978 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.fieldgroup; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.logging.Logger; + +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.TransactionalPropertyWrapper; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.DefaultFieldFactory; +import com.vaadin.ui.Field; +import com.vaadin.ui.Form; + +/** + * FieldGroup provides an easy way of binding fields to data and handling + * commits of these fields. + * <p> + * The functionality of FieldGroup is similar to {@link Form} but + * {@link FieldGroup} does not handle layouts in any way. The typical use case + * is to create a layout outside the FieldGroup and then use FieldGroup to bind + * the fields to a data source. + * </p> + * <p> + * {@link FieldGroup} is not a UI component so it cannot be added to a layout. + * Using the buildAndBind methods {@link FieldGroup} can create fields for you + * using a FieldGroupFieldFactory but you still have to add them to the correct + * position in your layout. + * </p> + * + * @author Vaadin Ltd + * @version @version@ + * @since 7.0 + */ +public class FieldGroup implements Serializable { + + private static final Logger logger = Logger.getLogger(FieldGroup.class + .getName()); + + private Item itemDataSource; + private boolean buffered = true; + + private boolean enabled = true; + private boolean readOnly = false; + + private HashMap<Object, Field<?>> propertyIdToField = new HashMap<Object, Field<?>>(); + private LinkedHashMap<Field<?>, Object> fieldToPropertyId = new LinkedHashMap<Field<?>, Object>(); + private List<CommitHandler> commitHandlers = new ArrayList<CommitHandler>(); + + /** + * The field factory used by builder methods. + */ + private FieldGroupFieldFactory fieldFactory = new DefaultFieldGroupFieldFactory(); + + /** + * Constructs a field binder. Use {@link #setItemDataSource(Item)} to set a + * data source for the field binder. + * + */ + public FieldGroup() { + + } + + /** + * Constructs a field binder that uses the given data source. + * + * @param itemDataSource + * The data source to bind the fields to + */ + public FieldGroup(Item itemDataSource) { + setItemDataSource(itemDataSource); + } + + /** + * Updates the item that is used by this FieldBinder. Rebinds all fields to + * the properties in the new item. + * + * @param itemDataSource + * The new item to use + */ + public void setItemDataSource(Item itemDataSource) { + this.itemDataSource = itemDataSource; + + for (Field<?> f : fieldToPropertyId.keySet()) { + bind(f, fieldToPropertyId.get(f)); + } + } + + /** + * Gets the item used by this FieldBinder. Note that you must call + * {@link #commit()} for the item to be updated unless buffered mode has + * been switched off. + * + * @see #setBuffered(boolean) + * @see #commit() + * + * @return The item used by this FieldBinder + */ + public Item getItemDataSource() { + return itemDataSource; + } + + /** + * Checks the buffered mode for the bound fields. + * <p> + * + * @see #setBuffered(boolean) for more details on buffered mode + * + * @see Field#isBuffered() + * @return true if buffered mode is on, false otherwise + * + */ + public boolean isBuffered() { + return buffered; + } + + /** + * Sets the buffered mode for the bound fields. + * <p> + * When buffered mode is on the item will not be updated until + * {@link #commit()} is called. If buffered mode is off the item will be + * updated once the fields are updated. + * </p> + * <p> + * The default is to use buffered mode. + * </p> + * + * @see Field#setBuffered(boolean) + * @param buffered + * true to turn on buffered mode, false otherwise + */ + public void setBuffered(boolean buffered) { + if (buffered == this.buffered) { + return; + } + + this.buffered = buffered; + for (Field<?> field : getFields()) { + field.setBuffered(buffered); + } + } + + /** + * Returns the enabled status for the fields. + * <p> + * Note that this will not accurately represent the enabled status of all + * fields if you change the enabled status of the fields through some other + * method than {@link #setEnabled(boolean)}. + * + * @return true if the fields are enabled, false otherwise + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Updates the enabled state of all bound fields. + * + * @param fieldsEnabled + * true to enable all bound fields, false to disable them + */ + public void setEnabled(boolean fieldsEnabled) { + enabled = fieldsEnabled; + for (Field<?> field : getFields()) { + field.setEnabled(fieldsEnabled); + } + } + + /** + * Returns the read only status for the fields. + * <p> + * Note that this will not accurately represent the read only status of all + * fields if you change the read only status of the fields through some + * other method than {@link #setReadOnly(boolean)}. + * + * @return true if the fields are set to read only, false otherwise + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Updates the read only state of all bound fields. + * + * @param fieldsReadOnly + * true to set all bound fields to read only, false to set them + * to read write + */ + public void setReadOnly(boolean fieldsReadOnly) { + readOnly = fieldsReadOnly; + } + + /** + * Returns a collection of all fields that have been bound. + * <p> + * The fields are not returned in any specific order. + * </p> + * + * @return A collection with all bound Fields + */ + public Collection<Field<?>> getFields() { + return fieldToPropertyId.keySet(); + } + + /** + * Binds the field with the given propertyId from the current item. If an + * item has not been set then the binding is postponed until the item is set + * using {@link #setItemDataSource(Item)}. + * <p> + * This method also adds validators when applicable. + * </p> + * + * @param field + * The field to bind + * @param propertyId + * The propertyId to bind to the field + * @throws BindException + * If the property id is already bound to another field by this + * field binder + */ + public void bind(Field<?> field, Object propertyId) throws BindException { + if (propertyIdToField.containsKey(propertyId) + && propertyIdToField.get(propertyId) != field) { + throw new BindException("Property id " + propertyId + + " is already bound to another field"); + } + fieldToPropertyId.put(field, propertyId); + propertyIdToField.put(propertyId, field); + if (itemDataSource == null) { + // Will be bound when data source is set + return; + } + + field.setPropertyDataSource(wrapInTransactionalProperty(getItemProperty(propertyId))); + configureField(field); + } + + private <T> Property.Transactional<T> wrapInTransactionalProperty( + Property<T> itemProperty) { + return new TransactionalPropertyWrapper<T>(itemProperty); + } + + /** + * Gets the property with the given property id from the item. + * + * @param propertyId + * The id if the property to find + * @return The property with the given id from the item + * @throws BindException + * If the property was not found in the item or no item has been + * set + */ + protected Property<?> getItemProperty(Object propertyId) + throws BindException { + Item item = getItemDataSource(); + if (item == null) { + throw new BindException("Could not lookup property with id " + + propertyId + " as no item has been set"); + } + Property<?> p = item.getItemProperty(propertyId); + if (p == null) { + throw new BindException("A property with id " + propertyId + + " was not found in the item"); + } + return p; + } + + /** + * Detaches the field from its property id and removes it from this + * FieldBinder. + * <p> + * Note that the field is not detached from its property data source if it + * is no longer connected to the same property id it was bound to using this + * FieldBinder. + * + * @param field + * The field to detach + * @throws BindException + * If the field is not bound by this field binder or not bound + * to the correct property id + */ + public void unbind(Field<?> field) throws BindException { + Object propertyId = fieldToPropertyId.get(field); + if (propertyId == null) { + throw new BindException( + "The given field is not part of this FieldBinder"); + } + + Property fieldDataSource = field.getPropertyDataSource(); + if (fieldDataSource instanceof TransactionalPropertyWrapper) { + fieldDataSource = ((TransactionalPropertyWrapper) fieldDataSource) + .getWrappedProperty(); + } + if (fieldDataSource == getItemProperty(propertyId)) { + field.setPropertyDataSource(null); + } + fieldToPropertyId.remove(field); + propertyIdToField.remove(propertyId); + } + + /** + * Configures a field with the settings set for this FieldBinder. + * <p> + * By default this updates the buffered, read only and enabled state of the + * field. Also adds validators when applicable. + * + * @param field + * The field to update + */ + protected void configureField(Field<?> field) { + field.setBuffered(isBuffered()); + + field.setEnabled(isEnabled()); + field.setReadOnly(isReadOnly()); + } + + /** + * Gets the type of the property with the given property id. + * + * @param propertyId + * The propertyId. Must be find + * @return The type of the property + */ + protected Class<?> getPropertyType(Object propertyId) throws BindException { + if (getItemDataSource() == null) { + throw new BindException( + "Property type for '" + + propertyId + + "' could not be determined. No item data source has been set."); + } + Property<?> p = getItemDataSource().getItemProperty(propertyId); + if (p == null) { + throw new BindException( + "Property type for '" + + propertyId + + "' could not be determined. No property with that id was found."); + } + + return p.getType(); + } + + /** + * Returns a collection of all property ids that have been bound to fields. + * <p> + * Note that this will return property ids even before the item has been + * set. In that case it returns the property ids that will be bound once the + * item is set. + * </p> + * <p> + * No guarantee is given for the order of the property ids + * </p> + * + * @return A collection of bound property ids + */ + public Collection<Object> getBoundPropertyIds() { + return Collections.unmodifiableCollection(propertyIdToField.keySet()); + } + + /** + * Returns a collection of all property ids that exist in the item set using + * {@link #setItemDataSource(Item)} but have not been bound to fields. + * <p> + * Will always return an empty collection before an item has been set using + * {@link #setItemDataSource(Item)}. + * </p> + * <p> + * No guarantee is given for the order of the property ids + * </p> + * + * @return A collection of property ids that have not been bound to fields + */ + public Collection<Object> getUnboundPropertyIds() { + if (getItemDataSource() == null) { + return new ArrayList<Object>(); + } + List<Object> unboundPropertyIds = new ArrayList<Object>(); + unboundPropertyIds.addAll(getItemDataSource().getItemPropertyIds()); + unboundPropertyIds.removeAll(propertyIdToField.keySet()); + return unboundPropertyIds; + } + + /** + * Commits all changes done to the bound fields. + * <p> + * Calls all {@link CommitHandler}s before and after committing the field + * changes to the item data source. The whole commit is aborted and state is + * restored to what it was before commit was called if any + * {@link CommitHandler} throws a CommitException or there is a problem + * committing the fields + * + * @throws CommitException + * If the commit was aborted + */ + public void commit() throws CommitException { + if (!isBuffered()) { + // Not using buffered mode, nothing to do + return; + } + for (Field<?> f : fieldToPropertyId.keySet()) { + ((Property.Transactional<?>) f.getPropertyDataSource()) + .startTransaction(); + } + try { + firePreCommitEvent(); + // Commit the field values to the properties + for (Field<?> f : fieldToPropertyId.keySet()) { + f.commit(); + } + firePostCommitEvent(); + + // Commit the properties + for (Field<?> f : fieldToPropertyId.keySet()) { + ((Property.Transactional<?>) f.getPropertyDataSource()) + .commit(); + } + + } catch (Exception e) { + for (Field<?> f : fieldToPropertyId.keySet()) { + try { + ((Property.Transactional<?>) f.getPropertyDataSource()) + .rollback(); + } catch (Exception rollbackException) { + // FIXME: What to do ? + } + } + + throw new CommitException("Commit failed", e); + } + + } + + /** + * Sends a preCommit event to all registered commit handlers + * + * @throws CommitException + * If the commit should be aborted + */ + private void firePreCommitEvent() throws CommitException { + CommitHandler[] handlers = commitHandlers + .toArray(new CommitHandler[commitHandlers.size()]); + + for (CommitHandler handler : handlers) { + handler.preCommit(new CommitEvent(this)); + } + } + + /** + * Sends a postCommit event to all registered commit handlers + * + * @throws CommitException + * If the commit should be aborted + */ + private void firePostCommitEvent() throws CommitException { + CommitHandler[] handlers = commitHandlers + .toArray(new CommitHandler[commitHandlers.size()]); + + for (CommitHandler handler : handlers) { + handler.postCommit(new CommitEvent(this)); + } + } + + /** + * Discards all changes done to the bound fields. + * <p> + * Only has effect if buffered mode is used. + * + */ + public void discard() { + for (Field<?> f : fieldToPropertyId.keySet()) { + try { + f.discard(); + } catch (Exception e) { + // TODO: handle exception + // What can we do if discard fails other than try to discard all + // other fields? + } + } + } + + /** + * Returns the field that is bound to the given property id + * + * @param propertyId + * The property id to use to lookup the field + * @return The field that is bound to the property id or null if no field is + * bound to that property id + */ + public Field<?> getField(Object propertyId) { + return propertyIdToField.get(propertyId); + } + + /** + * Returns the property id that is bound to the given field + * + * @param field + * The field to use to lookup the property id + * @return The property id that is bound to the field or null if the field + * is not bound to any property id by this FieldBinder + */ + public Object getPropertyId(Field<?> field) { + return fieldToPropertyId.get(field); + } + + /** + * Adds a commit handler. + * <p> + * The commit handler is called before the field values are committed to the + * item ( {@link CommitHandler#preCommit(CommitEvent)}) and after the item + * has been updated ({@link CommitHandler#postCommit(CommitEvent)}). If a + * {@link CommitHandler} throws a CommitException the whole commit is + * aborted and the fields retain their old values. + * + * @param commitHandler + * The commit handler to add + */ + public void addCommitHandler(CommitHandler commitHandler) { + commitHandlers.add(commitHandler); + } + + /** + * Removes the given commit handler. + * + * @see #addCommitHandler(CommitHandler) + * + * @param commitHandler + * The commit handler to remove + */ + public void removeCommitHandler(CommitHandler commitHandler) { + commitHandlers.remove(commitHandler); + } + + /** + * Returns a list of all commit handlers for this {@link FieldGroup}. + * <p> + * Use {@link #addCommitHandler(CommitHandler)} and + * {@link #removeCommitHandler(CommitHandler)} to register or unregister a + * commit handler. + * + * @return A collection of commit handlers + */ + protected Collection<CommitHandler> getCommitHandlers() { + return Collections.unmodifiableCollection(commitHandlers); + } + + /** + * CommitHandlers are used by {@link FieldGroup#commit()} as part of the + * commit transactions. CommitHandlers can perform custom operations as part + * of the commit and cause the commit to be aborted by throwing a + * {@link CommitException}. + */ + public interface CommitHandler extends Serializable { + /** + * Called before changes are committed to the field and the item is + * updated. + * <p> + * Throw a {@link CommitException} to abort the commit. + * + * @param commitEvent + * An event containing information regarding the commit + * @throws CommitException + * if the commit should be aborted + */ + public void preCommit(CommitEvent commitEvent) throws CommitException; + + /** + * Called after changes are committed to the fields and the item is + * updated.. + * <p> + * Throw a {@link CommitException} to abort the commit. + * + * @param commitEvent + * An event containing information regarding the commit + * @throws CommitException + * if the commit should be aborted + */ + public void postCommit(CommitEvent commitEvent) throws CommitException; + } + + /** + * FIXME javadoc + * + */ + public static class CommitEvent implements Serializable { + private FieldGroup fieldBinder; + + private CommitEvent(FieldGroup fieldBinder) { + this.fieldBinder = fieldBinder; + } + + /** + * Returns the field binder that this commit relates to + * + * @return The FieldBinder that is being committed. + */ + public FieldGroup getFieldBinder() { + return fieldBinder; + } + + } + + /** + * Checks the validity of the bound fields. + * <p> + * Call the {@link Field#validate()} for the fields to get the individual + * error messages. + * + * @return true if all bound fields are valid, false otherwise. + */ + public boolean isValid() { + try { + for (Field<?> field : getFields()) { + field.validate(); + } + return true; + } catch (InvalidValueException e) { + return false; + } + } + + /** + * Checks if any bound field has been modified. + * + * @return true if at least on field has been modified, false otherwise + */ + public boolean isModified() { + for (Field<?> field : getFields()) { + if (field.isModified()) { + return true; + } + } + return false; + } + + /** + * Gets the field factory for the {@link FieldGroup}. The field factory is + * only used when {@link FieldGroup} creates a new field. + * + * @return The field factory in use + * + */ + public FieldGroupFieldFactory getFieldFactory() { + return fieldFactory; + } + + /** + * Sets the field factory for the {@link FieldGroup}. The field factory is + * only used when {@link FieldGroup} creates a new field. + * + * @param fieldFactory + * The field factory to use + */ + public void setFieldFactory(FieldGroupFieldFactory fieldFactory) { + this.fieldFactory = fieldFactory; + } + + /** + * Binds member fields found in the given object. + * <p> + * This method processes all (Java) member fields whose type extends + * {@link Field} and that can be mapped to a property id. Property id + * mapping is done based on the field name or on a @{@link PropertyId} + * annotation on the field. All non-null fields for which a property id can + * be determined are bound to the property id. + * </p> + * <p> + * For example: + * + * <pre> + * public class MyForm extends VerticalLayout { + * private TextField firstName = new TextField("First name"); + * @PropertyId("last") + * private TextField lastName = new TextField("Last name"); + * private TextField age = new TextField("Age"); ... } + * + * MyForm myForm = new MyForm(); + * ... + * fieldGroup.bindMemberFields(myForm); + * </pre> + * + * </p> + * This binds the firstName TextField to a "firstName" property in the item, + * lastName TextField to a "last" property and the age TextField to a "age" + * property. + * + * @param objectWithMemberFields + * The object that contains (Java) member fields to bind + * @throws BindException + * If there is a problem binding a field + */ + public void bindMemberFields(Object objectWithMemberFields) + throws BindException { + buildAndBindMemberFields(objectWithMemberFields, false); + } + + /** + * Binds member fields found in the given object and builds member fields + * that have not been initialized. + * <p> + * This method processes all (Java) member fields whose type extends + * {@link Field} and that can be mapped to a property id. Property id + * mapping is done based on the field name or on a @{@link PropertyId} + * annotation on the field. Fields that are not initialized (null) are built + * using the field factory. All non-null fields for which a property id can + * be determined are bound to the property id. + * </p> + * <p> + * For example: + * + * <pre> + * public class MyForm extends VerticalLayout { + * private TextField firstName = new TextField("First name"); + * @PropertyId("last") + * private TextField lastName = new TextField("Last name"); + * private TextField age; + * + * MyForm myForm = new MyForm(); + * ... + * fieldGroup.buildAndBindMemberFields(myForm); + * </pre> + * + * </p> + * <p> + * This binds the firstName TextField to a "firstName" property in the item, + * lastName TextField to a "last" property and builds an age TextField using + * the field factory and then binds it to the "age" property. + * </p> + * + * @param objectWithMemberFields + * The object that contains (Java) member fields to build and + * bind + * @throws BindException + * If there is a problem binding or building a field + */ + public void buildAndBindMemberFields(Object objectWithMemberFields) + throws BindException { + buildAndBindMemberFields(objectWithMemberFields, true); + } + + /** + * Binds member fields found in the given object and optionally builds + * member fields that have not been initialized. + * <p> + * This method processes all (Java) member fields whose type extends + * {@link Field} and that can be mapped to a property id. Property id + * mapping is done based on the field name or on a @{@link PropertyId} + * annotation on the field. Fields that are not initialized (null) are built + * using the field factory is buildFields is true. All non-null fields for + * which a property id can be determined are bound to the property id. + * </p> + * + * @param objectWithMemberFields + * The object that contains (Java) member fields to build and + * bind + * @throws BindException + * If there is a problem binding or building a field + */ + protected void buildAndBindMemberFields(Object objectWithMemberFields, + boolean buildFields) throws BindException { + Class<?> objectClass = objectWithMemberFields.getClass(); + + for (java.lang.reflect.Field memberField : objectClass + .getDeclaredFields()) { + + if (!Field.class.isAssignableFrom(memberField.getType())) { + // Process next field + continue; + } + + PropertyId propertyIdAnnotation = memberField + .getAnnotation(PropertyId.class); + + Class<? extends Field> fieldType = (Class<? extends Field>) memberField + .getType(); + + Object propertyId = null; + if (propertyIdAnnotation != null) { + // @PropertyId(propertyId) always overrides property id + propertyId = propertyIdAnnotation.value(); + } else { + propertyId = memberField.getName(); + } + + // Ensure that the property id exists + Class<?> propertyType; + + try { + propertyType = getPropertyType(propertyId); + } catch (BindException e) { + // Property id was not found, skip this field + continue; + } + + Field<?> field; + try { + // Get the field from the object + field = (Field<?>) ReflectTools.getJavaFieldValue( + objectWithMemberFields, memberField); + } catch (Exception e) { + // If we cannot determine the value, just skip the field and try + // the next one + continue; + } + + if (field == null && buildFields) { + Caption captionAnnotation = memberField + .getAnnotation(Caption.class); + String caption; + if (captionAnnotation != null) { + caption = captionAnnotation.value(); + } else { + caption = DefaultFieldFactory + .createCaptionByPropertyId(propertyId); + } + + // Create the component (Field) + field = build(caption, propertyType, fieldType); + + // Store it in the field + try { + ReflectTools.setJavaFieldValue(objectWithMemberFields, + memberField, field); + } catch (IllegalArgumentException e) { + throw new BindException("Could not assign value to field '" + + memberField.getName() + "'", e); + } catch (IllegalAccessException e) { + throw new BindException("Could not assign value to field '" + + memberField.getName() + "'", e); + } catch (InvocationTargetException e) { + throw new BindException("Could not assign value to field '" + + memberField.getName() + "'", e); + } + } + + if (field != null) { + // Bind it to the property id + bind(field, propertyId); + } + } + } + + public static class CommitException extends Exception { + + public CommitException() { + super(); + // TODO Auto-generated constructor stub + } + + public CommitException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public CommitException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public CommitException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + } + + public static class BindException extends RuntimeException { + + public BindException(String message) { + super(message); + } + + public BindException(String message, Throwable t) { + super(message, t); + } + + } + + /** + * Builds a field and binds it to the given property id using the field + * binder. + * + * @param propertyId + * The property id to bind to. Must be present in the field + * finder. + * @throws BindException + * If there is a problem while building or binding + * @return The created and bound field + */ + public Field<?> buildAndBind(Object propertyId) throws BindException { + String caption = DefaultFieldFactory + .createCaptionByPropertyId(propertyId); + return buildAndBind(caption, propertyId); + } + + /** + * Builds a field using the given caption and binds it to the given property + * id using the field binder. + * + * @param caption + * The caption for the field + * @param propertyId + * The property id to bind to. Must be present in the field + * finder. + * @throws BindException + * If there is a problem while building or binding + * @return The created and bound field. Can be any type of {@link Field}. + */ + public Field<?> buildAndBind(String caption, Object propertyId) + throws BindException { + Class<?> type = getPropertyType(propertyId); + return buildAndBind(caption, propertyId, Field.class); + + } + + /** + * Builds a field using the given caption and binds it to the given property + * id using the field binder. Ensures the new field is of the given type. + * + * @param caption + * The caption for the field + * @param propertyId + * The property id to bind to. Must be present in the field + * finder. + * @throws BindException + * If the field could not be created + * @return The created and bound field. Can be any type of {@link Field}. + */ + + public <T extends Field> T buildAndBind(String caption, Object propertyId, + Class<T> fieldType) throws BindException { + Class<?> type = getPropertyType(propertyId); + + T field = build(caption, type, fieldType); + bind(field, propertyId); + + return field; + } + + /** + * Creates a field based on the given data type. + * <p> + * The data type is the type that we want to edit using the field. The field + * type is the type of field we want to create, can be {@link Field} if any + * Field is good. + * </p> + * + * @param caption + * The caption for the new field + * @param dataType + * The data model type that we want to edit using the field + * @param fieldType + * The type of field that we want to create + * @return A Field capable of editing the given type + * @throws BindException + * If the field could not be created + */ + protected <T extends Field> T build(String caption, Class<?> dataType, + Class<T> fieldType) throws BindException { + T field = getFieldFactory().createField(dataType, fieldType); + if (field == null) { + throw new BindException("Unable to build a field of type " + + fieldType.getName() + " for editing " + + dataType.getName()); + } + + field.setCaption(caption); + return field; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java b/src/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java new file mode 100644 index 0000000000..80c012cbdc --- /dev/null +++ b/src/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java @@ -0,0 +1,31 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.fieldgroup; + +import java.io.Serializable; + +import com.vaadin.ui.Field; + +/** + * Factory interface for creating new Field-instances based on the data type + * that should be edited. + * + * @author Vaadin Ltd. + * @version @version@ + * @since 7.0 + */ +public interface FieldGroupFieldFactory extends Serializable { + /** + * Creates a field based on the data type that we want to edit + * + * @param dataType + * The type that we want to edit using the field + * @param fieldType + * The type of field we want to create. If set to {@link Field} + * then any type of field is accepted + * @return A field that can be assigned to the given fieldType and that is + * capable of editing the given type of data + */ + <T extends Field> T createField(Class<?> dataType, Class<T> fieldType); +} diff --git a/src/com/vaadin/data/fieldgroup/PropertyId.java b/src/com/vaadin/data/fieldgroup/PropertyId.java new file mode 100644 index 0000000000..268047401d --- /dev/null +++ b/src/com/vaadin/data/fieldgroup/PropertyId.java @@ -0,0 +1,15 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.fieldgroup; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface PropertyId { + String value(); +} diff --git a/src/com/vaadin/data/util/AbstractBeanContainer.java b/src/com/vaadin/data/util/AbstractBeanContainer.java index 6260e05518..bed3ca0450 100644 --- a/src/com/vaadin/data/util/AbstractBeanContainer.java +++ b/src/com/vaadin/data/util/AbstractBeanContainer.java @@ -104,8 +104,9 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends + " not found"); } try { - Property property = pd.createProperty(bean); - return (IDTYPE) property.getValue(); + Property<IDTYPE> property = (Property<IDTYPE>) pd + .createProperty(bean); + return property.getValue(); } catch (MethodException e) { throw new IllegalArgumentException(e); } @@ -256,7 +257,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object, * java.lang.Object) */ - public Property getContainerProperty(Object itemId, Object propertyId) { + public Property<?> getContainerProperty(Object itemId, Object propertyId) { Item item = getItem(itemId); if (item == null) { return null; @@ -371,7 +372,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends * The id of the property */ private void addValueChangeListener(Item item, Object propertyId) { - Property property = item.getItemProperty(propertyId); + Property<?> property = item.getItemProperty(propertyId); if (property instanceof ValueChangeNotifier) { // avoid multiple notifications for the same property if // multiple filters are in use @@ -390,7 +391,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends * The id of the property */ private void removeValueChangeListener(Item item, Object propertyId) { - Property property = item.getItemProperty(propertyId); + Property<?> property = item.getItemProperty(propertyId); if (property instanceof ValueChangeNotifier) { ((ValueChangeNotifier) property).removeListener(this); } @@ -754,9 +755,9 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends } model.put(propertyId, propertyDescriptor); - for (BeanItem item : itemIdToItem.values()) { - item.addItemProperty(propertyId, propertyDescriptor - .createProperty((BEANTYPE) item.getBean())); + for (BeanItem<BEANTYPE> item : itemIdToItem.values()) { + item.addItemProperty(propertyId, + propertyDescriptor.createProperty(item.getBean())); } // Sends a change event @@ -775,7 +776,6 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends * @see NestedMethodProperty * * @param propertyId - * @param propertyType * @return true if the property was added */ public boolean addNestedContainerProperty(String propertyId) { @@ -783,6 +783,41 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends propertyId, type)); } + /** + * Adds a nested container properties for all sub-properties of a named + * property to the container. The named property itself is removed from the + * model as its subproperties are added. + * + * All intermediate getters must exist and must return non-null values when + * the property value is accessed. + * + * @see NestedMethodProperty + * @see #addNestedContainerProperty(String) + * + * @param propertyId + */ + @SuppressWarnings("unchecked") + public void addNestedContainerBean(String propertyId) { + Class<?> propertyType = getType(propertyId); + LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem + .getPropertyDescriptors((Class<Object>) propertyType); + for (String subPropertyId : pds.keySet()) { + String qualifiedPropertyId = propertyId + "." + subPropertyId; + NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>( + qualifiedPropertyId, (Class<BEANTYPE>) type); + model.put(qualifiedPropertyId, pd); + model.remove(propertyId); + for (BeanItem<BEANTYPE> item : itemIdToItem.values()) { + item.addItemProperty(propertyId, + pd.createProperty(item.getBean())); + item.removeItemProperty(propertyId); + } + } + + // Sends a change event + fireContainerPropertySetChange(); + } + @Override public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException { diff --git a/src/com/vaadin/data/util/AbstractProperty.java b/src/com/vaadin/data/util/AbstractProperty.java index f9c6faacf1..3b6db3807e 100644 --- a/src/com/vaadin/data/util/AbstractProperty.java +++ b/src/com/vaadin/data/util/AbstractProperty.java @@ -17,7 +17,7 @@ import com.vaadin.data.Property; * * @since 6.6 */ -public abstract class AbstractProperty implements Property, +public abstract class AbstractProperty<T> implements Property<T>, Property.ValueChangeNotifier, Property.ReadOnlyStatusChangeNotifier { /** @@ -56,18 +56,17 @@ public abstract class AbstractProperty implements Property, /** * Returns the value of the <code>Property</code> in human readable textual - * format. The return value should be assignable to the - * <code>setValue</code> method if the Property is not in read-only mode. + * format. * * @return String representation of the value stored in the Property + * @deprecated use {@link #getValue()} instead and possibly toString on that */ + @Deprecated @Override public String toString() { - final Object value = getValue(); - if (value == null) { - return null; - } - return value.toString(); + throw new UnsupportedOperationException( + "Use Property.getValue() instead of " + getClass() + + ".toString()"); } /* Events */ @@ -76,8 +75,8 @@ public abstract class AbstractProperty implements Property, * An <code>Event</code> object specifying the Property whose read-only * status has been changed. */ - protected class ReadOnlyStatusChangeEvent extends java.util.EventObject - implements Property.ReadOnlyStatusChangeEvent { + protected static class ReadOnlyStatusChangeEvent extends + java.util.EventObject implements Property.ReadOnlyStatusChangeEvent { /** * Constructs a new read-only status change event for this object. @@ -144,8 +143,8 @@ public abstract class AbstractProperty implements Property, * An <code>Event</code> object specifying the Property whose value has been * changed. */ - private class ValueChangeEvent extends java.util.EventObject implements - Property.ValueChangeEvent { + private static class ValueChangeEvent extends java.util.EventObject + implements Property.ValueChangeEvent { /** * Constructs a new value change event for this object. diff --git a/src/com/vaadin/data/util/BeanItem.java b/src/com/vaadin/data/util/BeanItem.java index ed59baa9f8..94439471f5 100644 --- a/src/com/vaadin/data/util/BeanItem.java +++ b/src/com/vaadin/data/util/BeanItem.java @@ -12,9 +12,11 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * A wrapper class for adding the Item interface to any Java Bean. @@ -162,7 +164,7 @@ public class BeanItem<BT> extends PropertysetItem { final Method getMethod = pd.getReadMethod(); if ((getMethod != null) && getMethod.getDeclaringClass() != Object.class) { - VaadinPropertyDescriptor<BT> vaadinPropertyDescriptor = new MethodPropertyDescriptor( + VaadinPropertyDescriptor<BT> vaadinPropertyDescriptor = new MethodPropertyDescriptor<BT>( pd.getName(), pd.getPropertyType(), pd.getReadMethod(), pd.getWriteMethod()); pdMap.put(pd.getName(), vaadinPropertyDescriptor); @@ -213,6 +215,49 @@ public class BeanItem<BT> extends PropertysetItem { } /** + * Expands nested bean properties by replacing a top-level property with + * some or all of its sub-properties. The expansion is not recursive. + * + * @param propertyId + * property id for the property whose sub-properties are to be + * expanded, + * @param subPropertyIds + * sub-properties to expand, all sub-properties are expanded if + * not specified + */ + public void expandProperty(String propertyId, String... subPropertyIds) { + Set<String> subPropertySet = new HashSet<String>( + Arrays.asList(subPropertyIds)); + + if (0 == subPropertyIds.length) { + // Enumerate all sub-properties + Class<?> propertyType = getItemProperty(propertyId).getType(); + Map<String, ?> pds = getPropertyDescriptors(propertyType); + subPropertySet.addAll(pds.keySet()); + } + + for (String subproperty : subPropertySet) { + String qualifiedPropertyId = propertyId + "." + subproperty; + addNestedProperty(qualifiedPropertyId); + } + + removeItemProperty(propertyId); + } + + /** + * Adds a nested property to the item. + * + * @param nestedPropertyId + * property id to add. This property must not exist in the item + * already and must of of form "field1.field2" where field2 is a + * field in the object referenced to by field1 + */ + public void addNestedProperty(String nestedPropertyId) { + addItemProperty(nestedPropertyId, new NestedMethodProperty<Object>( + getBean(), nestedPropertyId)); + } + + /** * Gets the underlying JavaBean object. * * @return the bean object. diff --git a/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java index e5972f697e..91950f5d4f 100644 --- a/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java +++ b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java @@ -641,7 +641,7 @@ public class ContainerHierarchicalWrapper implements Container.Hierarchical, * Container Don't add a JavaDoc comment here, we use the default * documentation from implemented interface. */ - public Property getContainerProperty(Object itemId, Object propertyId) { + public Property<?> getContainerProperty(Object itemId, Object propertyId) { return container.getContainerProperty(itemId, propertyId); } diff --git a/src/com/vaadin/data/util/ContainerOrderedWrapper.java b/src/com/vaadin/data/util/ContainerOrderedWrapper.java index 1600699362..f333edecf4 100644 --- a/src/com/vaadin/data/util/ContainerOrderedWrapper.java +++ b/src/com/vaadin/data/util/ContainerOrderedWrapper.java @@ -437,7 +437,7 @@ public class ContainerOrderedWrapper implements Container.Ordered, * Container Don't add a JavaDoc comment here, we use the default * documentation from implemented interface. */ - public Property getContainerProperty(Object itemId, Object propertyId) { + public Property<?> getContainerProperty(Object itemId, Object propertyId) { return container.getContainerProperty(itemId, propertyId); } diff --git a/src/com/vaadin/data/util/DefaultItemSorter.java b/src/com/vaadin/data/util/DefaultItemSorter.java index 9b834f4a2e..47db5d7507 100644 --- a/src/com/vaadin/data/util/DefaultItemSorter.java +++ b/src/com/vaadin/data/util/DefaultItemSorter.java @@ -122,8 +122,8 @@ public class DefaultItemSorter implements ItemSorter { Item item1, Item item2) { // Get the properties to compare - final Property property1 = item1.getItemProperty(propertyId); - final Property property2 = item2.getItemProperty(propertyId); + final Property<?> property1 = item1.getItemProperty(propertyId); + final Property<?> property2 = item2.getItemProperty(propertyId); // Get the values to compare final Object value1 = (property1 == null) ? null : property1.getValue(); diff --git a/src/com/vaadin/data/util/FilesystemContainer.java b/src/com/vaadin/data/util/FilesystemContainer.java index 8e9873334b..7100286712 100644 --- a/src/com/vaadin/data/util/FilesystemContainer.java +++ b/src/com/vaadin/data/util/FilesystemContainer.java @@ -459,7 +459,7 @@ public class FilesystemContainer implements Container.Hierarchical { * the property's ID. * @return the requested property's value, or <code>null</code> */ - public Property getContainerProperty(Object itemId, Object propertyId) { + public Property<?> getContainerProperty(Object itemId, Object propertyId) { if (!(itemId instanceof File)) { return null; @@ -609,7 +609,7 @@ public class FilesystemContainer implements Container.Hierarchical { * Gets the specified property of this file. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ - public Property getItemProperty(Object id) { + public Property<?> getItemProperty(Object id) { return getContainerProperty(file, id); } diff --git a/src/com/vaadin/ui/treetable/HierarchicalContainerOrderedWrapper.java b/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapper.java index f826c59bf7..b7eac3e378 100644 --- a/src/com/vaadin/ui/treetable/HierarchicalContainerOrderedWrapper.java +++ b/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapper.java @@ -1,18 +1,20 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.ui.treetable; +package com.vaadin.data.util; import java.util.Collection; import com.vaadin.data.Container.Hierarchical; -import com.vaadin.data.util.ContainerOrderedWrapper; -@SuppressWarnings({ "serial", "unchecked" }) /** - * Helper for TreeTable. Does the same thing as ContainerOrderedWrapper - * to fit into table but retains Hierarchical feature. + * A wrapper class for adding external ordering to containers not implementing + * the {@link com.vaadin.data.Container.Ordered} interface while retaining + * {@link Hierarchical} features. + * + * @see ContainerOrderedWrapper */ +@SuppressWarnings({ "serial" }) public class HierarchicalContainerOrderedWrapper extends ContainerOrderedWrapper implements Hierarchical { diff --git a/src/com/vaadin/data/util/IndexedContainer.java b/src/com/vaadin/data/util/IndexedContainer.java index 9728c79864..1e0a2fae1a 100644 --- a/src/com/vaadin/data/util/IndexedContainer.java +++ b/src/com/vaadin/data/util/IndexedContainer.java @@ -5,7 +5,6 @@ package com.vaadin.data.util; import java.io.Serializable; -import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -76,7 +75,7 @@ public class IndexedContainer extends /** * Set of properties that are read-only. */ - private HashSet<Property> readOnlyProperties = new HashSet<Property>(); + private HashSet<Property<?>> readOnlyProperties = new HashSet<Property<?>>(); /** * List of all Property value change event listeners listening all the @@ -150,7 +149,7 @@ public class IndexedContainer extends * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object, * java.lang.Object) */ - public Property getContainerProperty(Object itemId, Object propertyId) { + public Property<?> getContainerProperty(Object itemId, Object propertyId) { if (!containsId(itemId)) { return null; } @@ -425,7 +424,7 @@ public class IndexedContainer extends * @VERSION@ * @since 3.0 */ - public class ItemSetChangeEvent extends BaseItemSetChangeEvent { + public static class ItemSetChangeEvent extends BaseItemSetChangeEvent { private final int addedItemIndex; @@ -455,7 +454,7 @@ public class IndexedContainer extends * @VERSION@ * @since 3.0 */ - private class PropertyValueChangeEvent extends EventObject implements + private static class PropertyValueChangeEvent extends EventObject implements Property.ValueChangeEvent, Serializable { private PropertyValueChangeEvent(Property source) { @@ -680,7 +679,7 @@ public class IndexedContainer extends * * @see com.vaadin.data.Item#getItemProperty(java.lang.Object) */ - public Property getItemProperty(Object id) { + public Property<?> getItemProperty(Object id) { return new IndexedContainerProperty(itemId, id); } @@ -691,8 +690,8 @@ public class IndexedContainer extends /** * Gets the <code>String</code> representation of the contents of the * Item. The format of the string is a space separated catenation of the - * <code>String</code> representations of the Properties contained by - * the Item. + * <code>String</code> representations of the values of the Properties + * contained by the Item. * * @return <code>String</code> representation of the Item contents */ @@ -702,7 +701,7 @@ public class IndexedContainer extends for (final Iterator<?> i = propertyIds.iterator(); i.hasNext();) { final Object propertyId = i.next(); - retValue += getItemProperty(propertyId).toString(); + retValue += getItemProperty(propertyId).getValue(); if (i.hasNext()) { retValue += " "; } @@ -786,7 +785,7 @@ public class IndexedContainer extends * @VERSION@ * @since 3.0 */ - private class IndexedContainerProperty implements Property, + private class IndexedContainerProperty implements Property<Object>, Property.ValueChangeNotifier { /** @@ -865,8 +864,7 @@ public class IndexedContainer extends * * @see com.vaadin.data.Property#setValue(java.lang.Object) */ - public void setValue(Object newValue) - throws Property.ReadOnlyException, Property.ConversionException { + public void setValue(Object newValue) throws Property.ReadOnlyException { // Gets the Property set final Map<Object, Object> propertySet = items.get(itemId); @@ -877,22 +875,8 @@ public class IndexedContainer extends } else if (getType().isAssignableFrom(newValue.getClass())) { propertySet.put(propertyId, newValue); } else { - try { - - // Gets the string constructor - final Constructor<?> constr = getType().getConstructor( - new Class[] { String.class }); - - // Creates new object from the string - propertySet.put(propertyId, constr - .newInstance(new Object[] { newValue.toString() })); - - } catch (final java.lang.Exception e) { - throw new Property.ConversionException( - "Conversion for value '" + newValue + "' of class " - + newValue.getClass().getName() + " to " - + getType().getName() + " failed", e); - } + throw new IllegalArgumentException("Value is of invalid type, " + + getType().getName() + " expected"); } // update the container filtering if this property is being filtered @@ -910,14 +894,14 @@ public class IndexedContainer extends * * @return <code>String</code> representation of the value stored in the * Property + * @deprecated use {@link #getValue()} instead and possibly toString on + * that */ + @Deprecated @Override public String toString() { - final Object value = getValue(); - if (value == null) { - return null; - } - return value.toString(); + throw new UnsupportedOperationException( + "Use Property.getValue() instead of IndexedContainerProperty.toString()"); } /** @@ -1038,7 +1022,7 @@ public class IndexedContainer extends getPropertySetChangeListeners()) : null); nc.propertyValueChangeListeners = propertyValueChangeListeners != null ? (LinkedList<Property.ValueChangeListener>) propertyValueChangeListeners .clone() : null; - nc.readOnlyProperties = readOnlyProperties != null ? (HashSet<Property>) readOnlyProperties + nc.readOnlyProperties = readOnlyProperties != null ? (HashSet<Property<?>>) readOnlyProperties .clone() : null; nc.singlePropertyValueChangeListeners = singlePropertyValueChangeListeners != null ? (Hashtable<Object, Map<Object, List<Property.ValueChangeListener>>>) singlePropertyValueChangeListeners .clone() : null; @@ -1097,4 +1081,4 @@ public class IndexedContainer extends removeFilter(filter); } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/data/util/MethodProperty.java b/src/com/vaadin/data/util/MethodProperty.java index ff258d3e0f..4fc5531320 100644 --- a/src/com/vaadin/data/util/MethodProperty.java +++ b/src/com/vaadin/data/util/MethodProperty.java @@ -5,7 +5,6 @@ package com.vaadin.data.util; import java.io.IOException; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.logging.Level; @@ -47,7 +46,7 @@ import com.vaadin.util.SerializerHelper; * @since 3.0 */ @SuppressWarnings("serial") -public class MethodProperty<T> extends AbstractProperty { +public class MethodProperty<T> extends AbstractProperty<T> { private static final Logger logger = Logger.getLogger(MethodProperty.class .getName()); @@ -170,7 +169,7 @@ public class MethodProperty<T> extends AbstractProperty { @SuppressWarnings("unchecked") public MethodProperty(Object instance, String beanPropertyName) { - final Class beanClass = instance.getClass(); + final Class<?> beanClass = instance.getClass(); // Assure that the first letter is upper cased (it is a common // mistake to write firstName, not FirstName). @@ -349,7 +348,7 @@ public class MethodProperty<T> extends AbstractProperty { } // Tests the parameter types - final Class[] c = m[i].getParameterTypes(); + final Class<?>[] c = m[i].getParameterTypes(); if (c.length != getArgs.length) { // not the right amount of parameters, try next method @@ -398,7 +397,7 @@ public class MethodProperty<T> extends AbstractProperty { } // Checks parameter compatibility - final Class[] c = m[i].getParameterTypes(); + final Class<?>[] c = m[i].getParameterTypes(); if (c.length != setArgs.length) { // not the right amount of parameters, try next method @@ -474,7 +473,9 @@ public class MethodProperty<T> extends AbstractProperty { * {@link #setValue(Object newValue)} is called. */ @SuppressWarnings("unchecked") - public MethodProperty(Class type, Object instance, Method getMethod, + // cannot use "Class<? extends T>" because of automatic primitive type + // conversions + public MethodProperty(Class<?> type, Object instance, Method getMethod, Method setMethod, Object[] getArgs, Object[] setArgs, int setArgumentIndex) { @@ -495,13 +496,13 @@ public class MethodProperty<T> extends AbstractProperty { } // Gets the return type from get method - type = convertPrimitiveType(type); + Class<? extends T> convertedType = (Class<? extends T>) convertPrimitiveType(type); this.getMethod = getMethod; this.setMethod = setMethod; setArguments(getArgs, setArgs, setArgumentIndex); this.instance = instance; - this.type = type; + this.type = convertedType; } /** @@ -569,8 +570,7 @@ public class MethodProperty<T> extends AbstractProperty { * * @return type of the Property */ - @SuppressWarnings("unchecked") - public final Class getType() { + public final Class<? extends T> getType() { return type; } @@ -593,9 +593,9 @@ public class MethodProperty<T> extends AbstractProperty { * * @return the value of the Property */ - public Object getValue() { + public T getValue() { try { - return getMethod.invoke(instance, getArgs); + return (T) getMethod.invoke(instance, getArgs); } catch (final Throwable e) { throw new MethodException(this, e); } @@ -629,61 +629,33 @@ public class MethodProperty<T> extends AbstractProperty { } /** - * Sets the value of the property. This method supports setting from - * <code>String</code>s if either <code>String</code> is directly assignable - * to property type, or the type class contains a string constructor. + * Sets the value of the property. + * + * Note that since Vaadin 7, no conversions are performed and the value must + * be of the correct type. * * @param newValue * the New value of the property. * @throws <code>Property.ReadOnlyException</code> if the object is in * read-only mode. - * @throws <code>Property.ConversionException</code> if - * <code>newValue</code> can't be converted into the Property's - * native type directly or through <code>String</code>. * @see #invokeSetMethod(Object) */ @SuppressWarnings("unchecked") - public void setValue(Object newValue) throws Property.ReadOnlyException, - Property.ConversionException { + public void setValue(Object newValue) throws Property.ReadOnlyException { // Checks the mode if (isReadOnly()) { throw new Property.ReadOnlyException(); } - Object value = convertValue(newValue, type); - - invokeSetMethod(value); - fireValueChange(); - } - - /** - * Convert a value to the given type, using a constructor of the type that - * takes a single String parameter (toString() for the value) if necessary. - * - * @param value - * to convert - * @param type - * type into which the value should be converted - * @return converted value - */ - static Object convertValue(Object value, Class<?> type) { - if (null == value || type.isAssignableFrom(value.getClass())) { - return value; + // Checks the type of the value + if (newValue != null && !type.isAssignableFrom(newValue.getClass())) { + throw new IllegalArgumentException( + "Invalid value type for ObjectProperty."); } - // convert using a string constructor - try { - // Gets the string constructor - final Constructor constr = type - .getConstructor(new Class[] { String.class }); - - // Create a new object from the string - return constr.newInstance(new Object[] { value.toString() }); - - } catch (final java.lang.Exception e) { - throw new Property.ConversionException(e); - } + invokeSetMethod((T) newValue); + fireValueChange(); } /** @@ -692,7 +664,7 @@ public class MethodProperty<T> extends AbstractProperty { * * @param value */ - protected void invokeSetMethod(Object value) { + protected void invokeSetMethod(T value) { try { // Construct a temporary argument array only if needed diff --git a/src/com/vaadin/data/util/MethodPropertyDescriptor.java b/src/com/vaadin/data/util/MethodPropertyDescriptor.java index f0c879766b..10faa7a0f3 100644 --- a/src/com/vaadin/data/util/MethodPropertyDescriptor.java +++ b/src/com/vaadin/data/util/MethodPropertyDescriptor.java @@ -123,9 +123,9 @@ public class MethodPropertyDescriptor<BT> implements return propertyType; } - public Property createProperty(Object bean) { + public Property<?> createProperty(Object bean) { return new MethodProperty<Object>(propertyType, bean, readMethod, writeMethod); } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/data/util/NestedMethodProperty.java b/src/com/vaadin/data/util/NestedMethodProperty.java index 8f5a17af16..d7b0f44912 100644 --- a/src/com/vaadin/data/util/NestedMethodProperty.java +++ b/src/com/vaadin/data/util/NestedMethodProperty.java @@ -26,7 +26,7 @@ import com.vaadin.data.util.MethodProperty.MethodException; * * @since 6.6 */ -public class NestedMethodProperty extends AbstractProperty { +public class NestedMethodProperty<T> extends AbstractProperty<T> { // needed for de-serialization private String propertyName; @@ -43,7 +43,7 @@ public class NestedMethodProperty extends AbstractProperty { */ private Object instance; - private Class<?> type; + private Class<? extends T> type; /* Special serialization to handle method references */ private void writeObject(java.io.ObjectOutputStream out) throws IOException { @@ -158,13 +158,14 @@ public class NestedMethodProperty extends AbstractProperty { } catch (final NoSuchMethodException skipped) { } - this.type = MethodProperty.convertPrimitiveType(type); + this.type = (Class<? extends T>) MethodProperty + .convertPrimitiveType(type); this.propertyName = propertyName; this.getMethods = getMethods; this.setMethod = setMethod; } - public Class<?> getType() { + public Class<? extends T> getType() { return type; } @@ -179,42 +180,41 @@ public class NestedMethodProperty extends AbstractProperty { * * @return the value of the Property */ - public Object getValue() { + public T getValue() { try { Object object = instance; for (Method m : getMethods) { object = m.invoke(object); } - return object; + return (T) object; } catch (final Throwable e) { throw new MethodException(this, e); } } /** - * Sets the value of the property. This method supports setting from - * <code>String</code>s if either <code>String</code> is directly assignable - * to property type, or the type class contains a string constructor. + * Sets the value of the property. The new value must be assignable to the + * type of this property. * * @param newValue * the New value of the property. * @throws <code>Property.ReadOnlyException</code> if the object is in * read-only mode. - * @throws <code>Property.ConversionException</code> if - * <code>newValue</code> can't be converted into the Property's - * native type directly or through <code>String</code>. * @see #invokeSetMethod(Object) */ - public void setValue(Object newValue) throws ReadOnlyException, - ConversionException { + public void setValue(Object newValue) throws ReadOnlyException { // Checks the mode if (isReadOnly()) { throw new Property.ReadOnlyException(); } - Object value = MethodProperty.convertValue(newValue, type); + // Checks the type of the value + if (newValue != null && !type.isAssignableFrom(newValue.getClass())) { + throw new IllegalArgumentException( + "Invalid value type for NestedMethodProperty."); + } - invokeSetMethod(value); + invokeSetMethod((T) newValue); fireValueChange(); } @@ -224,7 +224,7 @@ public class NestedMethodProperty extends AbstractProperty { * * @param value */ - protected void invokeSetMethod(Object value) { + protected void invokeSetMethod(T value) { try { Object object = instance; for (int i = 0; i < getMethods.size() - 1; i++) { diff --git a/src/com/vaadin/data/util/NestedPropertyDescriptor.java b/src/com/vaadin/data/util/NestedPropertyDescriptor.java index abdb9e0cd3..6404f6361d 100644 --- a/src/com/vaadin/data/util/NestedPropertyDescriptor.java +++ b/src/com/vaadin/data/util/NestedPropertyDescriptor.java @@ -37,7 +37,8 @@ public class NestedPropertyDescriptor<BT> implements public NestedPropertyDescriptor(String name, Class<BT> beanType) throws IllegalArgumentException { this.name = name; - NestedMethodProperty property = new NestedMethodProperty(beanType, name); + NestedMethodProperty<?> property = new NestedMethodProperty<Object>( + beanType, name); this.propertyType = property.getType(); } @@ -49,8 +50,8 @@ public class NestedPropertyDescriptor<BT> implements return propertyType; } - public Property createProperty(BT bean) { - return new NestedMethodProperty(bean, name); + public Property<?> createProperty(BT bean) { + return new NestedMethodProperty<Object>(bean, name); } } diff --git a/src/com/vaadin/data/util/ObjectProperty.java b/src/com/vaadin/data/util/ObjectProperty.java index 4319ea7716..9c60b9146e 100644 --- a/src/com/vaadin/data/util/ObjectProperty.java +++ b/src/com/vaadin/data/util/ObjectProperty.java @@ -4,8 +4,6 @@ package com.vaadin.data.util; -import java.lang.reflect.Constructor; - import com.vaadin.data.Property; /** @@ -19,7 +17,7 @@ import com.vaadin.data.Property; * @since 3.0 */ @SuppressWarnings("serial") -public class ObjectProperty<T> extends AbstractProperty { +public class ObjectProperty<T> extends AbstractProperty<T> { /** * The value contained by the Property. @@ -48,9 +46,8 @@ public class ObjectProperty<T> extends AbstractProperty { /** * Creates a new instance of ObjectProperty with the given value and type. * - * Any value of type Object is accepted because, if the type class contains - * a string constructor, the toString of the value is used to create the new - * value. See {@link #setValue(Object)}. + * Since Vaadin 7, only values of the correct type are accepted, and no + * automatic conversions are performed. * * @param value * the Initial value of the Property. @@ -58,7 +55,7 @@ public class ObjectProperty<T> extends AbstractProperty { * the type of the value. The value must be assignable to given * type. */ - public ObjectProperty(Object value, Class<T> type) { + public ObjectProperty(T value, Class<T> type) { // Set the values this.type = type; @@ -69,7 +66,7 @@ public class ObjectProperty<T> extends AbstractProperty { * Creates a new instance of ObjectProperty with the given value, type and * read-only mode status. * - * Any value of type Object is accepted, see + * Since Vaadin 7, only the correct type of values is accepted, see * {@link #ObjectProperty(Object, Class)}. * * @param value @@ -80,7 +77,7 @@ public class ObjectProperty<T> extends AbstractProperty { * @param readOnly * Sets the read-only mode. */ - public ObjectProperty(Object value, Class<T> type, boolean readOnly) { + public ObjectProperty(T value, Class<T> type, boolean readOnly) { this(value, type); setReadOnly(readOnly); } @@ -108,49 +105,34 @@ public class ObjectProperty<T> extends AbstractProperty { } /** - * Sets the value of the property. This method supports setting from - * <code>String</code> if either <code>String</code> is directly assignable - * to property type, or the type class contains a string constructor. + * Sets the value of the property. + * + * Note that since Vaadin 7, no conversions are performed and the value must + * be of the correct type. * * @param newValue * the New value of the property. * @throws <code>Property.ReadOnlyException</code> if the object is in * read-only mode - * @throws <code>Property.ConversionException</code> if the newValue can't - * be converted into the Property's native type directly or through - * <code>String</code> */ - public void setValue(Object newValue) throws Property.ReadOnlyException, - Property.ConversionException { + @SuppressWarnings("unchecked") + public void setValue(Object newValue) throws Property.ReadOnlyException { // Checks the mode if (isReadOnly()) { throw new Property.ReadOnlyException(); } - // Tries to assign the compatible value directly - if (newValue == null || type.isAssignableFrom(newValue.getClass())) { - @SuppressWarnings("unchecked") - // the cast is safe after an isAssignableFrom check - T value = (T) newValue; - this.value = value; - } else { - try { - - // Gets the string constructor - final Constructor<T> constr = getType().getConstructor( - new Class[] { String.class }); - - // Creates new object from the string - value = constr - .newInstance(new Object[] { newValue.toString() }); - - } catch (final java.lang.Exception e) { - throw new Property.ConversionException(e); - } + // Checks the type of the value + if (newValue != null && !type.isAssignableFrom(newValue.getClass())) { + throw new IllegalArgumentException("Invalid value type " + + newValue.getClass().getName() + + " for ObjectProperty of type " + type.getName() + "."); } + // the cast is safe after an isAssignableFrom check + this.value = (T) newValue; + fireValueChange(); } - } diff --git a/src/com/vaadin/data/util/PropertyFormatter.java b/src/com/vaadin/data/util/PropertyFormatter.java index 1491f9a25e..a63973535b 100644 --- a/src/com/vaadin/data/util/PropertyFormatter.java +++ b/src/com/vaadin/data/util/PropertyFormatter.java @@ -4,6 +4,7 @@ package com.vaadin.data.util; import com.vaadin.data.Property; +import com.vaadin.data.util.converter.Converter; /** * Formatting proxy for a {@link Property}. @@ -29,16 +30,22 @@ import com.vaadin.data.Property; * standard "1.0" notation with more zeroes. * </p> * + * @param T + * type of the underlying property (a PropertyFormatter is always a + * Property<String>) + * + * @deprecated Since 7.0 replaced by {@link Converter} * @author Vaadin Ltd. * @since 5.3.0 */ @SuppressWarnings("serial") -public abstract class PropertyFormatter extends AbstractProperty implements - Property.Viewer, Property.ValueChangeListener, +@Deprecated +public abstract class PropertyFormatter<T> extends AbstractProperty<String> + implements Property.Viewer, Property.ValueChangeListener, Property.ReadOnlyStatusChangeListener { /** Datasource that stores the actual value. */ - Property dataSource; + Property<T> dataSource; /** * Construct a new {@code PropertyFormatter} that is not connected to any @@ -57,7 +64,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements * @param propertyDataSource * to connect this property to. */ - public PropertyFormatter(Property propertyDataSource) { + public PropertyFormatter(Property<T> propertyDataSource) { setPropertyDataSource(propertyDataSource); } @@ -68,7 +75,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements * @return the current data source as a Property, or <code>null</code> if * none defined. */ - public Property getPropertyDataSource() { + public Property<T> getPropertyDataSource() { return dataSource; } @@ -99,7 +106,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements .removeListener(this); } readOnly = isReadOnly(); - prevValue = toString(); + prevValue = getValue(); } dataSource = newDataSource; @@ -117,7 +124,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements if (isReadOnly() != readOnly) { fireReadOnlyStatusChange(); } - String newVal = toString(); + String newVal = getValue(); if ((prevValue == null && newVal != null) || (prevValue != null && !prevValue.equals(newVal))) { fireValueChange(); @@ -125,7 +132,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements } /* Documented in the interface */ - public Class getType() { + public Class<String> getType() { return String.class; } @@ -135,22 +142,8 @@ public abstract class PropertyFormatter extends AbstractProperty implements * @return If the datasource returns null, this is null. Otherwise this is * String given by format(). */ - public Object getValue() { - return toString(); - } - - /** - * Get the formatted value. - * - * @return If the datasource returns null, this is null. Otherwise this is - * String given by format(). - */ - @Override - public String toString() { - if (dataSource == null) { - return null; - } - Object value = dataSource.getValue(); + public String getValue() { + T value = dataSource == null ? null : dataSource.getValue(); if (value == null) { return null; } @@ -173,7 +166,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements * datasource. * @return */ - abstract public String format(Object value); + abstract public String format(T value); /** * Parse string and convert it to format compatible with datasource. @@ -187,7 +180,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements * Any type of exception can be thrown to indicate that the * conversion was not succesful. */ - abstract public Object parse(String formattedValue) throws Exception; + abstract public T parse(String formattedValue) throws Exception; /** * Sets the Property's read-only mode to the specified status. @@ -202,8 +195,7 @@ public abstract class PropertyFormatter extends AbstractProperty implements } } - public void setValue(Object newValue) throws ReadOnlyException, - ConversionException { + public void setValue(Object newValue) throws ReadOnlyException { if (dataSource == null) { return; } @@ -215,13 +207,11 @@ public abstract class PropertyFormatter extends AbstractProperty implements } else { try { dataSource.setValue(parse(newValue.toString())); - if (!newValue.equals(toString())) { + if (!newValue.equals(getValue())) { fireValueChange(); } - } catch (ConversionException e) { - throw e; } catch (Exception e) { - throw new ConversionException(e); + throw new IllegalArgumentException("Could not parse value", e); } } } diff --git a/src/com/vaadin/data/util/PropertysetItem.java b/src/com/vaadin/data/util/PropertysetItem.java index 04a7c66257..3270fa31f9 100644 --- a/src/com/vaadin/data/util/PropertysetItem.java +++ b/src/com/vaadin/data/util/PropertysetItem.java @@ -34,7 +34,7 @@ public class PropertysetItem implements Item, Item.PropertySetChangeNotifier, /** * Mapping from property id to property. */ - private HashMap<Object, Property> map = new HashMap<Object, Property>(); + private HashMap<Object, Property<?>> map = new HashMap<Object, Property<?>>(); /** * List of all property ids to maintain the order. @@ -57,7 +57,7 @@ public class PropertysetItem implements Item, Item.PropertySetChangeNotifier, * the identifier of the Property to get. * @return the Property with the given ID or <code>null</code> */ - public Property getItemProperty(Object id) { + public Property<?> getItemProperty(Object id) { return map.get(id); } @@ -143,7 +143,7 @@ public class PropertysetItem implements Item, Item.PropertySetChangeNotifier, for (final Iterator<?> i = getItemPropertyIds().iterator(); i.hasNext();) { final Object propertyId = i.next(); - retValue += getItemProperty(propertyId).toString(); + retValue += getItemProperty(propertyId).getValue(); if (i.hasNext()) { retValue += " "; } @@ -163,7 +163,7 @@ public class PropertysetItem implements Item, Item.PropertySetChangeNotifier, * @VERSION@ * @since 3.0 */ - private class PropertySetChangeEvent extends EventObject implements + private static class PropertySetChangeEvent extends EventObject implements Item.PropertySetChangeEvent { private PropertySetChangeEvent(Item source) { @@ -262,7 +262,7 @@ public class PropertysetItem implements Item, Item.PropertySetChangeNotifier, npsi.list = list != null ? (LinkedList<Object>) list.clone() : null; npsi.propertySetChangeListeners = propertySetChangeListeners != null ? (LinkedList<PropertySetChangeListener>) propertySetChangeListeners .clone() : null; - npsi.map = (HashMap<Object, Property>) map.clone(); + npsi.map = (HashMap<Object, Property<?>>) map.clone(); return npsi; } diff --git a/src/com/vaadin/data/util/QueryContainer.java b/src/com/vaadin/data/util/QueryContainer.java index 2281343c30..7fef63e7f1 100644 --- a/src/com/vaadin/data/util/QueryContainer.java +++ b/src/com/vaadin/data/util/QueryContainer.java @@ -136,7 +136,8 @@ public class QueryContainer implements Container, Container.Ordered, for (int i = 1; i <= count; i++) { final String columnName = metadata.getColumnName(i); list.add(columnName); - final Property p = getContainerProperty(new Integer(1), columnName); + final Property<?> p = getContainerProperty(new Integer(1), + columnName); propertyTypes.put(columnName, p == null ? Object.class : p.getType()); } @@ -228,7 +229,7 @@ public class QueryContainer implements Container, Container.Ordered, * otherwise. */ - public synchronized Property getContainerProperty(Object itemId, + public synchronized Property<?> getContainerProperty(Object itemId, Object propertyId) { if (!(itemId instanceof Integer && propertyId instanceof String)) { return null; @@ -531,7 +532,7 @@ public class QueryContainer implements Container, Container.Ordered, * identifier of the Property to get * @return the Property with the given ID or <code>null</code> */ - public Property getItemProperty(Object propertyId) { + public Property<?> getItemProperty(Object propertyId) { return getContainerProperty(id, propertyId); } diff --git a/src/com/vaadin/data/util/TextFileProperty.java b/src/com/vaadin/data/util/TextFileProperty.java index cfa8d4fabf..5ebba98062 100644 --- a/src/com/vaadin/data/util/TextFileProperty.java +++ b/src/com/vaadin/data/util/TextFileProperty.java @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * */ @SuppressWarnings("serial") -public class TextFileProperty extends AbstractProperty { +public class TextFileProperty extends AbstractProperty<String> { private File file; private Charset charset = null; @@ -64,7 +64,7 @@ public class TextFileProperty extends AbstractProperty { * * @see com.vaadin.data.Property#getType() */ - public Class<?> getType() { + public Class<String> getType() { return String.class; } @@ -73,7 +73,7 @@ public class TextFileProperty extends AbstractProperty { * * @see com.vaadin.data.Property#getValue() */ - public Object getValue() { + public String getValue() { if (file == null) { return null; } diff --git a/src/com/vaadin/data/util/TransactionalPropertyWrapper.java b/src/com/vaadin/data/util/TransactionalPropertyWrapper.java new file mode 100644 index 0000000000..06ec0935c3 --- /dev/null +++ b/src/com/vaadin/data/util/TransactionalPropertyWrapper.java @@ -0,0 +1,107 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.util; + +import com.vaadin.data.Property; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeNotifier; + +/** + * Wrapper class that helps implement two-phase commit for a non-transactional + * property. + * + * When accessing the property through the wrapper, getting and setting the + * property value take place immediately. However, the wrapper keeps track of + * the old value of the property so that it can be set for the property in case + * of a roll-back. This can result in the underlying property value changing + * multiple times (first based on modifications made by the application, then + * back upon roll-back). + * + * Value change events on the {@link TransactionalPropertyWrapper} are only + * fired at the end of a successful transaction, whereas listeners attached to + * the underlying property may receive multiple value change events. + * + * @see com.vaadin.data.Property.Transactional + * + * @author Vaadin Ltd + * @version @version@ + * @since 7.0 + * + * @param <T> + */ +public class TransactionalPropertyWrapper<T> extends AbstractProperty<T> + implements ValueChangeNotifier, Property.Transactional<T> { + + private Property<T> wrappedProperty; + private boolean inTransaction = false; + private boolean valueChangePending; + private T valueBeforeTransaction; + + public TransactionalPropertyWrapper(Property<T> wrappedProperty) { + this.wrappedProperty = wrappedProperty; + if (wrappedProperty instanceof ValueChangeNotifier) { + ((ValueChangeNotifier) wrappedProperty) + .addListener(new ValueChangeListener() { + + public void valueChange(ValueChangeEvent event) { + fireValueChange(); + } + }); + } + } + + public Class getType() { + return wrappedProperty.getType(); + } + + public T getValue() { + return wrappedProperty.getValue(); + } + + public void setValue(Object newValue) throws ReadOnlyException { + // Causes a value change to be sent to this listener which in turn fires + // a new value change event for this property + wrappedProperty.setValue(newValue); + } + + public void startTransaction() { + inTransaction = true; + valueBeforeTransaction = getValue(); + } + + public void commit() { + endTransaction(); + } + + public void rollback() { + try { + wrappedProperty.setValue(valueBeforeTransaction); + } finally { + valueChangePending = false; + endTransaction(); + } + } + + protected void endTransaction() { + inTransaction = false; + valueBeforeTransaction = null; + if (valueChangePending) { + fireValueChange(); + } + } + + @Override + protected void fireValueChange() { + if (inTransaction) { + valueChangePending = true; + } else { + super.fireValueChange(); + } + } + + public Property<T> getWrappedProperty() { + return wrappedProperty; + } + +} diff --git a/src/com/vaadin/data/util/VaadinPropertyDescriptor.java b/src/com/vaadin/data/util/VaadinPropertyDescriptor.java index 2a28671881..ee1e525540 100644 --- a/src/com/vaadin/data/util/VaadinPropertyDescriptor.java +++ b/src/com/vaadin/data/util/VaadinPropertyDescriptor.java @@ -39,5 +39,5 @@ public interface VaadinPropertyDescriptor<BT> extends Serializable { * @param bean * @return */ - public Property createProperty(BT bean); -}
\ No newline at end of file + public Property<?> createProperty(BT bean); +} diff --git a/src/com/vaadin/data/util/converter/Converter.java b/src/com/vaadin/data/util/converter/Converter.java new file mode 100644 index 0000000000..b8c15e8cdc --- /dev/null +++ b/src/com/vaadin/data/util/converter/Converter.java @@ -0,0 +1,159 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.io.Serializable; +import java.util.Locale; + +/** + * Interface that implements conversion between a model and a presentation type. + * <p> + * Typically {@link #convertToPresentation(Object, Locale)} and + * {@link #convertToModel(Object, Locale)} should be symmetric so that chaining + * these together returns the original result for all input but this is not a + * requirement. + * </p> + * <p> + * Converters must not have any side effects (never update UI from inside a + * converter). + * </p> + * <p> + * All Converters must be stateless and thread safe. + * </p> + * <p> + * If conversion of a value fails, a {@link ConversionException} is thrown. + * </p> + * + * @param <MODEL> + * The model type. Must be compatible with what + * {@link #getModelType()} returns. + * @param <PRESENTATION> + * The presentation type. Must be compatible with what + * {@link #getPresentationType()} returns. + * @author Vaadin Ltd. + * @version + * @VERSION@ + * @since 7.0 + */ +public interface Converter<PRESENTATION, MODEL> extends Serializable { + + /** + * Converts the given value from target type to source type. + * <p> + * A converter can optionally use locale to do the conversion. + * </p> + * A converter should in most cases be symmetric so chaining + * {@link #convertToPresentation(Object, Locale)} and + * {@link #convertToModel(Object, Locale)} should return the original value. + * + * @param value + * The value to convert, compatible with the target type. Can be + * null + * @param locale + * The locale to use for conversion. Can be null. + * @return The converted value compatible with the source type + * @throws ConversionException + * If the value could not be converted + */ + public MODEL convertToModel(PRESENTATION value, Locale locale) + throws ConversionException; + + /** + * Converts the given value from source type to target type. + * <p> + * A converter can optionally use locale to do the conversion. + * </p> + * A converter should in most cases be symmetric so chaining + * {@link #convertToPresentation(Object, Locale)} and + * {@link #convertToModel(Object, Locale)} should return the original value. + * + * @param value + * The value to convert, compatible with the target type. Can be + * null + * @param locale + * The locale to use for conversion. Can be null. + * @return The converted value compatible with the source type + * @throws ConversionException + * If the value could not be converted + */ + public PRESENTATION convertToPresentation(MODEL value, Locale locale) + throws ConversionException; + + /** + * The source type of the converter. + * + * Values of this type can be passed to + * {@link #convertToPresentation(Object, Locale)}. + * + * @return The source type + */ + public Class<MODEL> getModelType(); + + /** + * The target type of the converter. + * + * Values of this type can be passed to + * {@link #convertToModel(Object, Locale)}. + * + * @return The target type + */ + public Class<PRESENTATION> getPresentationType(); + + /** + * An exception that signals that the value passed to + * {@link Converter#convertToPresentation(Object, Locale)} or + * {@link Converter#convertToModel(Object, Locale)} could not be converted. + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ + public static class ConversionException extends RuntimeException { + + /** + * Constructs a new <code>ConversionException</code> without a detail + * message. + */ + public ConversionException() { + } + + /** + * Constructs a new <code>ConversionException</code> with the specified + * detail message. + * + * @param msg + * the detail message + */ + public ConversionException(String msg) { + super(msg); + } + + /** + * Constructs a new {@code ConversionException} with the specified + * cause. + * + * @param cause + * The cause of the the exception + */ + public ConversionException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new <code>ConversionException</code> with the specified + * detail message and cause. + * + * @param message + * the detail message + * @param cause + * The cause of the the exception + */ + public ConversionException(String message, Throwable cause) { + super(message, cause); + } + } + +} diff --git a/src/com/vaadin/data/util/converter/ConverterFactory.java b/src/com/vaadin/data/util/converter/ConverterFactory.java new file mode 100644 index 0000000000..ed4ab41ac0 --- /dev/null +++ b/src/com/vaadin/data/util/converter/ConverterFactory.java @@ -0,0 +1,23 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.io.Serializable; + +/** + * Factory interface for providing Converters based on a presentation type and a + * model type. + * + * @author Vaadin Ltd. + * @version + * @VERSION@ + * @since 7.0 + * + */ +public interface ConverterFactory extends Serializable { + public <PRESENTATION, MODEL> Converter<PRESENTATION, MODEL> createConverter( + Class<PRESENTATION> presentationType, Class<MODEL> modelType); + +} diff --git a/src/com/vaadin/data/util/converter/DateToLongConverter.java b/src/com/vaadin/data/util/converter/DateToLongConverter.java new file mode 100644 index 0000000000..537800f617 --- /dev/null +++ b/src/com/vaadin/data/util/converter/DateToLongConverter.java @@ -0,0 +1,68 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.util.Date; +import java.util.Locale; + +/** + * A converter that converts from {@link Long} to {@link Date} and back. + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class DateToLongConverter implements Converter<Date, Long> { + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, + * java.util.Locale) + */ + public Long convertToModel(Date value, Locale locale) { + if (value == null) { + return null; + } + + return value.getTime(); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang + * .Object, java.util.Locale) + */ + public Date convertToPresentation(Long value, Locale locale) { + if (value == null) { + return null; + } + + return new Date(value); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getModelType() + */ + public Class<Long> getModelType() { + return Long.class; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getPresentationType() + */ + public Class<Date> getPresentationType() { + return Date.class; + } + +} diff --git a/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/src/com/vaadin/data/util/converter/DefaultConverterFactory.java new file mode 100644 index 0000000000..3ad7b6a85b --- /dev/null +++ b/src/com/vaadin/data/util/converter/DefaultConverterFactory.java @@ -0,0 +1,100 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.util.Date; +import java.util.logging.Logger; + +import com.vaadin.Application; + +/** + * Default implementation of {@link ConverterFactory}. Provides converters for + * standard types like {@link String}, {@link Double} and {@link Date}. </p> + * <p> + * Custom converters can be provided by extending this class and using + * {@link Application#setConverterFactory(ConverterFactory)}. + * </p> + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class DefaultConverterFactory implements ConverterFactory { + + private final static Logger log = Logger + .getLogger(DefaultConverterFactory.class.getName()); + + public <PRESENTATION, MODEL> Converter<PRESENTATION, MODEL> createConverter( + Class<PRESENTATION> presentationType, Class<MODEL> modelType) { + Converter<PRESENTATION, MODEL> converter = findConverter( + presentationType, modelType); + if (converter != null) { + log.finest(getClass().getName() + " created a " + + converter.getClass()); + return converter; + } + + // Try to find a reverse converter + Converter<MODEL, PRESENTATION> reverseConverter = findConverter( + modelType, presentationType); + if (reverseConverter != null) { + log.finest(getClass().getName() + " created a reverse " + + reverseConverter.getClass()); + return new ReverseConverter<PRESENTATION, MODEL>(reverseConverter); + } + + log.finest(getClass().getName() + " could not find a converter for " + + presentationType.getName() + " to " + modelType.getName() + + " conversion"); + return null; + + } + + protected <PRESENTATION, MODEL> Converter<PRESENTATION, MODEL> findConverter( + Class<PRESENTATION> presentationType, Class<MODEL> modelType) { + if (presentationType == String.class) { + // TextField converters and more + Converter<PRESENTATION, MODEL> converter = (Converter<PRESENTATION, MODEL>) createStringConverter(modelType); + if (converter != null) { + return converter; + } + } else if (presentationType == Date.class) { + // DateField converters and more + Converter<PRESENTATION, MODEL> converter = (Converter<PRESENTATION, MODEL>) createDateConverter(modelType); + if (converter != null) { + return converter; + } + } + + return null; + + } + + protected Converter<Date, ?> createDateConverter(Class<?> sourceType) { + if (Long.class.isAssignableFrom(sourceType)) { + return new DateToLongConverter(); + } else { + return null; + } + } + + protected Converter<String, ?> createStringConverter(Class<?> sourceType) { + if (Double.class.isAssignableFrom(sourceType)) { + return new StringToDoubleConverter(); + } else if (Integer.class.isAssignableFrom(sourceType)) { + return new StringToIntegerConverter(); + } else if (Boolean.class.isAssignableFrom(sourceType)) { + return new StringToBooleanConverter(); + } else if (Number.class.isAssignableFrom(sourceType)) { + return new StringToNumberConverter(); + } else if (Date.class.isAssignableFrom(sourceType)) { + return new StringToDateConverter(); + } else { + return null; + } + } + +} diff --git a/src/com/vaadin/data/util/converter/ReverseConverter.java b/src/com/vaadin/data/util/converter/ReverseConverter.java new file mode 100644 index 0000000000..1c561f29e8 --- /dev/null +++ b/src/com/vaadin/data/util/converter/ReverseConverter.java @@ -0,0 +1,80 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.util.Locale; + +/** + * A converter that wraps another {@link Converter} and reverses source and + * target types. + * + * @param <MODEL> + * The source type + * @param <PRESENTATION> + * The target type + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class ReverseConverter<PRESENTATION, MODEL> implements + Converter<PRESENTATION, MODEL> { + + private Converter<MODEL, PRESENTATION> realConverter; + + /** + * Creates a converter from source to target based on a converter that + * converts from target to source. + * + * @param converter + * The converter to use in a reverse fashion + */ + public ReverseConverter(Converter<MODEL, PRESENTATION> converter) { + this.realConverter = converter; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#convertToModel(java + * .lang.Object, java.util.Locale) + */ + public MODEL convertToModel(PRESENTATION value, Locale locale) + throws com.vaadin.data.util.converter.Converter.ConversionException { + return realConverter.convertToPresentation(value, locale); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang + * .Object, java.util.Locale) + */ + public PRESENTATION convertToPresentation(MODEL value, Locale locale) + throws com.vaadin.data.util.converter.Converter.ConversionException { + return realConverter.convertToModel(value, locale); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getSourceType() + */ + public Class<MODEL> getModelType() { + return realConverter.getPresentationType(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getTargetType() + */ + public Class<PRESENTATION> getPresentationType() { + return realConverter.getModelType(); + } + +} diff --git a/src/com/vaadin/data/util/converter/StringToBooleanConverter.java b/src/com/vaadin/data/util/converter/StringToBooleanConverter.java new file mode 100644 index 0000000000..96a3a3d071 --- /dev/null +++ b/src/com/vaadin/data/util/converter/StringToBooleanConverter.java @@ -0,0 +1,104 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.util.Locale; + +/** + * A converter that converts from {@link String} to {@link Boolean} and back. + * The String representation is given by Boolean.toString(). + * <p> + * Leading and trailing white spaces are ignored when converting from a String. + * </p> + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class StringToBooleanConverter implements Converter<String, Boolean> { + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, + * java.util.Locale) + */ + public Boolean convertToModel(String value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + // Remove leading and trailing white space + value = value.trim(); + + if (getTrueString().equals(value)) { + return true; + } else if (getFalseString().equals(value)) { + return false; + } else { + throw new ConversionException("Cannot convert " + value + " to " + + getModelType().getName()); + } + } + + /** + * Gets the string representation for true. Default is "true". + * + * @return the string representation for true + */ + protected String getTrueString() { + return Boolean.TRUE.toString(); + } + + /** + * Gets the string representation for false. Default is "false". + * + * @return the string representation for false + */ + protected String getFalseString() { + return Boolean.FALSE.toString(); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang + * .Object, java.util.Locale) + */ + public String convertToPresentation(Boolean value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + if (value) { + return getTrueString(); + } else { + return getFalseString(); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getModelType() + */ + public Class<Boolean> getModelType() { + return Boolean.class; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getPresentationType() + */ + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/src/com/vaadin/data/util/converter/StringToDateConverter.java b/src/com/vaadin/data/util/converter/StringToDateConverter.java new file mode 100644 index 0000000000..6f3c2e47f6 --- /dev/null +++ b/src/com/vaadin/data/util/converter/StringToDateConverter.java @@ -0,0 +1,108 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.text.DateFormat; +import java.text.ParsePosition; +import java.util.Date; +import java.util.Locale; + +/** + * A converter that converts from {@link Date} to {@link String} and back. Uses + * the given locale and {@link DateFormat} for formatting and parsing. + * <p> + * Leading and trailing white spaces are ignored when converting from a String. + * </p> + * <p> + * Override and overwrite {@link #getFormat(Locale)} to use a different format. + * </p> + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class StringToDateConverter implements Converter<String, Date> { + + /** + * Returns the format used by {@link #convertToPresentation(Date, Locale)} + * and {@link #convertToModel(String, Locale)}. + * + * @param locale + * The locale to use + * @return A DateFormat instance + */ + protected DateFormat getFormat(Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + + DateFormat f = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, + DateFormat.MEDIUM, locale); + f.setLenient(false); + return f; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, + * java.util.Locale) + */ + public Date convertToModel(String value, Locale locale) + throws com.vaadin.data.util.converter.Converter.ConversionException { + if (value == null) { + return null; + } + + // Remove leading and trailing white space + value = value.trim(); + + ParsePosition parsePosition = new ParsePosition(0); + Date parsedValue = getFormat(locale).parse(value, parsePosition); + if (parsePosition.getIndex() != value.length()) { + throw new ConversionException("Could not convert '" + value + + "' to " + getModelType().getName()); + } + + return parsedValue; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang + * .Object, java.util.Locale) + */ + public String convertToPresentation(Date value, Locale locale) + throws com.vaadin.data.util.converter.Converter.ConversionException { + if (value == null) { + return null; + } + + return getFormat(locale).format(value); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getModelType() + */ + public Class<Date> getModelType() { + return Date.class; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getPresentationType() + */ + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/src/com/vaadin/data/util/converter/StringToDoubleConverter.java b/src/com/vaadin/data/util/converter/StringToDoubleConverter.java new file mode 100644 index 0000000000..60a38f4127 --- /dev/null +++ b/src/com/vaadin/data/util/converter/StringToDoubleConverter.java @@ -0,0 +1,103 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * A converter that converts from {@link String} to {@link Double} and back. + * Uses the given locale and a {@link NumberFormat} instance for formatting and + * parsing. + * <p> + * Leading and trailing white spaces are ignored when converting from a String. + * </p> + * <p> + * Override and overwrite {@link #getFormat(Locale)} to use a different format. + * </p> + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class StringToDoubleConverter implements Converter<String, Double> { + + /** + * Returns the format used by {@link #convertToPresentation(Double, Locale)} + * and {@link #convertToModel(String, Locale)}. + * + * @param locale + * The locale to use + * @return A NumberFormat instance + */ + protected NumberFormat getFormat(Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + + return NumberFormat.getNumberInstance(locale); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, + * java.util.Locale) + */ + public Double convertToModel(String value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + // Remove leading and trailing white space + value = value.trim(); + + ParsePosition parsePosition = new ParsePosition(0); + Number parsedValue = getFormat(locale).parse(value, parsePosition); + if (parsePosition.getIndex() != value.length()) { + throw new ConversionException("Could not convert '" + value + + "' to " + getModelType().getName()); + } + return parsedValue.doubleValue(); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang + * .Object, java.util.Locale) + */ + public String convertToPresentation(Double value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + return getFormat(locale).format(value); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getModelType() + */ + public Class<Double> getModelType() { + return Double.class; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getPresentationType() + */ + public Class<String> getPresentationType() { + return String.class; + } +} diff --git a/src/com/vaadin/data/util/converter/StringToIntegerConverter.java b/src/com/vaadin/data/util/converter/StringToIntegerConverter.java new file mode 100644 index 0000000000..e55feec3b6 --- /dev/null +++ b/src/com/vaadin/data/util/converter/StringToIntegerConverter.java @@ -0,0 +1,84 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * A converter that converts from {@link String} to {@link Integer} and back. + * Uses the given locale and a {@link NumberFormat} instance for formatting and + * parsing. + * <p> + * Override and overwrite {@link #getFormat(Locale)} to use a different format. + * </p> + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class StringToIntegerConverter implements Converter<String, Integer> { + + /** + * Returns the format used by + * {@link #convertToPresentation(Integer, Locale)} and + * {@link #convertToModel(String, Locale)}. + * + * @param locale + * The locale to use + * @return A NumberFormat instance + */ + protected NumberFormat getFormat(Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + return NumberFormat.getIntegerInstance(locale); + } + + public Integer convertToModel(String value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + // Remove leading and trailing white space + value = value.trim(); + + // Parse and detect errors. If the full string was not used, it is + // an error. + ParsePosition parsePosition = new ParsePosition(0); + Number parsedValue = getFormat(locale).parse(value, parsePosition); + if (parsePosition.getIndex() != value.length()) { + throw new ConversionException("Could not convert '" + value + + "' to " + getModelType().getName()); + } + + if (parsedValue == null) { + // Convert "" to null + return null; + } + return parsedValue.intValue(); + } + + public String convertToPresentation(Integer value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + return getFormat(locale).format(value); + } + + public Class<Integer> getModelType() { + return Integer.class; + } + + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/src/com/vaadin/data/util/converter/StringToNumberConverter.java b/src/com/vaadin/data/util/converter/StringToNumberConverter.java new file mode 100644 index 0000000000..d1816007e7 --- /dev/null +++ b/src/com/vaadin/data/util/converter/StringToNumberConverter.java @@ -0,0 +1,107 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.util.converter; + +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * A converter that converts from {@link Number} to {@link String} and back. + * Uses the given locale and {@link NumberFormat} for formatting and parsing. + * <p> + * Override and overwrite {@link #getFormat(Locale)} to use a different format. + * </p> + * + * @author Vaadin Ltd + * @version + * @VERSION@ + * @since 7.0 + */ +public class StringToNumberConverter implements Converter<String, Number> { + + /** + * Returns the format used by {@link #convertToPresentation(Number, Locale)} + * and {@link #convertToModel(String, Locale)}. + * + * @param locale + * The locale to use + * @return A NumberFormat instance + */ + protected NumberFormat getFormat(Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + + return NumberFormat.getNumberInstance(locale); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, + * java.util.Locale) + */ + public Number convertToModel(String value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + // Remove leading and trailing white space + value = value.trim(); + + // Parse and detect errors. If the full string was not used, it is + // an error. + ParsePosition parsePosition = new ParsePosition(0); + Number parsedValue = getFormat(locale).parse(value, parsePosition); + if (parsePosition.getIndex() != value.length()) { + throw new ConversionException("Could not convert '" + value + + "' to " + getModelType().getName()); + } + + if (parsedValue == null) { + // Convert "" to null + return null; + } + return parsedValue; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang + * .Object, java.util.Locale) + */ + public String convertToPresentation(Number value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + return getFormat(locale).format(value); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getModelType() + */ + public Class<Number> getModelType() { + return Number.class; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getPresentationType() + */ + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/src/com/vaadin/data/util/filter/Compare.java b/src/com/vaadin/data/util/filter/Compare.java index fe7d908c93..111d95f055 100644 --- a/src/com/vaadin/data/util/filter/Compare.java +++ b/src/com/vaadin/data/util/filter/Compare.java @@ -228,7 +228,7 @@ public abstract class Compare implements Filter { } public boolean passesFilter(Object itemId, Item item) { - final Property p = item.getItemProperty(getPropertyId()); + final Property<?> p = item.getItemProperty(getPropertyId()); if (null == p) { return false; } diff --git a/src/com/vaadin/data/util/filter/IsNull.java b/src/com/vaadin/data/util/filter/IsNull.java index 0ae00d2e09..aad71a7c80 100644 --- a/src/com/vaadin/data/util/filter/IsNull.java +++ b/src/com/vaadin/data/util/filter/IsNull.java @@ -35,7 +35,7 @@ public final class IsNull implements Filter { public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException { - final Property p = item.getItemProperty(getPropertyId()); + final Property<?> p = item.getItemProperty(getPropertyId()); if (null == p) { return false; } diff --git a/src/com/vaadin/data/util/filter/SimpleStringFilter.java b/src/com/vaadin/data/util/filter/SimpleStringFilter.java index 243571582e..6203251045 100644 --- a/src/com/vaadin/data/util/filter/SimpleStringFilter.java +++ b/src/com/vaadin/data/util/filter/SimpleStringFilter.java @@ -40,12 +40,16 @@ public final class SimpleStringFilter implements Filter { } public boolean passesFilter(Object itemId, Item item) { - final Property p = item.getItemProperty(propertyId); - if (p == null || p.toString() == null) { + final Property<?> p = item.getItemProperty(propertyId); + if (p == null) { return false; } - final String value = ignoreCase ? p.toString().toLowerCase() : p - .toString(); + Object propertyValue = p.getValue(); + if (propertyValue == null) { + return false; + } + final String value = ignoreCase ? propertyValue.toString() + .toLowerCase() : propertyValue.toString(); if (onlyMatchPrefix) { if (!value.startsWith(filterString)) { return false; diff --git a/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java b/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java index 85ff60d5d9..d84a164bfa 100644 --- a/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java +++ b/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java @@ -3,7 +3,6 @@ */ package com.vaadin.data.util.sqlcontainer; -import java.lang.reflect.Constructor; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -69,8 +68,7 @@ final public class ColumnProperty implements Property { return value; } - public void setValue(Object newValue) throws ReadOnlyException, - ConversionException { + public void setValue(Object newValue) throws ReadOnlyException { if (newValue == null && !nullable) { throw new NotNullableException( "Null values are not allowed for this property."); @@ -109,19 +107,9 @@ final public class ColumnProperty implements Property { } } - /* - * If the type is not correct, try to generate it through a possibly - * existing String constructor. - */ if (!getType().isAssignableFrom(newValue.getClass())) { - try { - final Constructor<?> constr = getType().getConstructor( - new Class[] { String.class }); - newValue = constr.newInstance(new Object[] { newValue - .toString() }); - } catch (Exception e) { - throw new ConversionException(e); - } + throw new IllegalArgumentException( + "Illegal value type for ColumnProperty"); } /* @@ -168,13 +156,17 @@ final public class ColumnProperty implements Property { return propertyId; } + /** + * Returns the value of the Property in human readable textual format. + * + * @see java.lang.Object#toString() + * @deprecated get the string representation from the value + */ + @Deprecated @Override public String toString() { - Object val = getValue(); - if (val == null) { - return null; - } - return val.toString(); + throw new UnsupportedOperationException( + "Use ColumnProperty.getValue() instead of ColumnProperty.toString()"); } public void setOwner(RowItem owner) { diff --git a/src/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java b/src/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java index 248b159aa9..adfd439ac8 100644 --- a/src/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java +++ b/src/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java @@ -3,6 +3,8 @@ */ package com.vaadin.data.util.sqlcontainer; +import com.vaadin.data.util.sqlcontainer.query.TableQuery; + /** * An OptimisticLockException is thrown when trying to update or delete a row * that has been changed since last read from the database. @@ -12,7 +14,7 @@ package com.vaadin.data.util.sqlcontainer; * configuration. In order to turn on optimistic locking, you need to specify * the version column in your TableQuery instance. * - * @see com.vaadin.addon.sqlcontainer.query.TableQuery#setVersionColumn(String) + * @see TableQuery#setVersionColumn(String) * * @author Jonatan Kronqvist / Vaadin Ltd */ diff --git a/src/com/vaadin/data/util/sqlcontainer/RowItem.java b/src/com/vaadin/data/util/sqlcontainer/RowItem.java index 3bd73bc48f..adededb65c 100644 --- a/src/com/vaadin/data/util/sqlcontainer/RowItem.java +++ b/src/com/vaadin/data/util/sqlcontainer/RowItem.java @@ -48,7 +48,7 @@ public final class RowItem implements Item { this.id = id; } - public Property getItemProperty(Object id) { + public Property<?> getItemProperty(Object id) { if (id instanceof String && id != null) { for (ColumnProperty cp : properties) { if (id.equals(cp.getPropertyId())) { @@ -113,7 +113,8 @@ public final class RowItem implements Item { s.append("|"); s.append(propId.toString()); s.append(":"); - s.append(getItemProperty(propId).toString()); + Object value = getItemProperty(propId).getValue(); + s.append((null != value) ? value.toString() : null); } return s.toString(); } diff --git a/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java b/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java index 7eb67437e0..3bf33defd5 100644 --- a/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java +++ b/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java @@ -227,7 +227,7 @@ public class SQLContainer implements Container, Container.Filterable, * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object, * java.lang.Object) */ - public Property getContainerProperty(Object itemId, Object propertyId) { + public Property<?> getContainerProperty(Object itemId, Object propertyId) { Item item = getItem(itemId); if (item == null) { return null; @@ -1435,7 +1435,7 @@ public class SQLContainer implements Container, Container.Filterable, * Simple ItemSetChangeEvent implementation. */ @SuppressWarnings("serial") - public class ItemSetChangeEvent extends EventObject implements + public static class ItemSetChangeEvent extends EventObject implements Container.ItemSetChangeEvent { private ItemSetChangeEvent(SQLContainer source) { @@ -1640,4 +1640,4 @@ public class SQLContainer implements Container, Container.Filterable, } } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java b/src/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java index 7dcab29611..56a8455a16 100644 --- a/src/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java +++ b/src/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java @@ -178,15 +178,14 @@ public class FreeformQuery implements QueryDelegate { /** * Fetches the results for the query. This implementation always fetches the - * entire record set, ignoring the offset and pagelength parameters. In + * entire record set, ignoring the offset and page length parameters. In * order to support lazy loading of records, you must supply a * FreeformQueryDelegate that implements the * FreeformQueryDelegate.getQueryString(int,int) method. * * @throws SQLException * - * @see com.vaadin.addon.sqlcontainer.query.FreeformQueryDelegate#getQueryString(int, - * int) {@inheritDoc} + * @see FreeformQueryDelegate#getQueryString(int, int) */ @SuppressWarnings("deprecation") public ResultSet getResults(int offset, int pagelength) throws SQLException { @@ -249,8 +248,8 @@ public class FreeformQuery implements QueryDelegate { * (non-Javadoc) * * @see - * com.vaadin.addon.sqlcontainer.query.QueryDelegate#setFilters(java.util - * .List) + * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#setFilters(java + * .util.List) */ public void setFilters(List<Filter> filters) throws UnsupportedOperationException { @@ -262,6 +261,13 @@ public class FreeformQuery implements QueryDelegate { } } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#setOrderBy(java + * .util.List) + */ public void setOrderBy(List<OrderBy> orderBys) throws UnsupportedOperationException { if (delegate != null) { @@ -272,6 +278,13 @@ public class FreeformQuery implements QueryDelegate { } } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#storeRow(com.vaadin + * .data.util.sqlcontainer.RowItem) + */ public int storeRow(RowItem row) throws SQLException { if (activeConnection == null) { throw new IllegalStateException("No transaction is active!"); @@ -287,6 +300,13 @@ public class FreeformQuery implements QueryDelegate { } } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#removeRow(com.vaadin + * .data.util.sqlcontainer.RowItem) + */ public boolean removeRow(RowItem row) throws SQLException { if (activeConnection == null) { throw new IllegalStateException("No transaction is active!"); @@ -302,6 +322,12 @@ public class FreeformQuery implements QueryDelegate { } } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#beginTransaction() + */ public synchronized void beginTransaction() throws UnsupportedOperationException, SQLException { if (activeConnection != null) { @@ -311,6 +337,11 @@ public class FreeformQuery implements QueryDelegate { activeConnection.setAutoCommit(false); } + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.sqlcontainer.query.QueryDelegate#commit() + */ public synchronized void commit() throws UnsupportedOperationException, SQLException { if (activeConnection == null) { @@ -323,6 +354,11 @@ public class FreeformQuery implements QueryDelegate { activeConnection = null; } + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.sqlcontainer.query.QueryDelegate#rollback() + */ public synchronized void rollback() throws UnsupportedOperationException, SQLException { if (activeConnection == null) { @@ -333,6 +369,13 @@ public class FreeformQuery implements QueryDelegate { activeConnection = null; } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#getPrimaryKeyColumns + * () + */ public List<String> getPrimaryKeyColumns() { return primaryKeyColumns; } @@ -357,9 +400,8 @@ public class FreeformQuery implements QueryDelegate { * getContainsRowQueryString method in FreeformQueryDelegate and this will * be used instead of the logic. * - * @see com.vaadin.addon.sqlcontainer.query.FreeformQueryDelegate#getContainsRowQueryString(Object...) + * @see FreeformQueryDelegate#getContainsRowQueryString(Object...) * - * {@inheritDoc} */ @SuppressWarnings("deprecation") public boolean containsRowWithKey(Object... keys) throws SQLException { diff --git a/src/com/vaadin/data/validator/AbstractStringValidator.java b/src/com/vaadin/data/validator/AbstractStringValidator.java index 3bc66b3a73..5267cc7b7b 100644 --- a/src/com/vaadin/data/validator/AbstractStringValidator.java +++ b/src/com/vaadin/data/validator/AbstractStringValidator.java @@ -4,9 +4,7 @@ package com.vaadin.data.validator; /** - * Validator base class for validating strings. See - * {@link com.vaadin.data.validator.AbstractValidator} for more information. - * + * Validator base class for validating strings. * <p> * To include the value that failed validation in the exception message you can * use "{0}" in the error message. This will be replaced with the failed value @@ -15,12 +13,11 @@ package com.vaadin.data.validator; * </p> * * @author Vaadin Ltd. - * @version - * @VERSION@ + * @version @VERSION@ * @since 5.4 */ @SuppressWarnings("serial") -public abstract class AbstractStringValidator extends AbstractValidator { +public abstract class AbstractStringValidator extends AbstractValidator<String> { /** * Constructs a validator for strings. @@ -38,35 +35,8 @@ public abstract class AbstractStringValidator extends AbstractValidator { super(errorMessage); } - /** - * Tests if the given value is a valid string. - * <p> - * Null values are always accepted. Values that are not {@link String}s are - * converted using {@link #toString()}. Then {@link #isValidString(String)} - * is used to validate the value. - * </p> - * - * @param value - * the value to check - * @return true if the value (or its toString()) is a valid string, false - * otherwise - */ - public boolean isValid(Object value) { - if (value == null) { - return true; - } - if (!(value instanceof String)) { - value = String.valueOf(value); - } - return isValidString((String) value); + @Override + public Class<String> getType() { + return String.class; } - - /** - * Checks if the given string is valid. - * - * @param value - * String to check. Can never be null. - * @return true if the string is valid, false otherwise - */ - protected abstract boolean isValidString(String value); } diff --git a/src/com/vaadin/data/validator/AbstractValidator.java b/src/com/vaadin/data/validator/AbstractValidator.java index 5c8dd9b31a..27eaaca485 100644 --- a/src/com/vaadin/data/validator/AbstractValidator.java +++ b/src/com/vaadin/data/validator/AbstractValidator.java @@ -7,8 +7,8 @@ import com.vaadin.data.Validator; /** * Abstract {@link com.vaadin.data.Validator Validator} implementation that - * provides a basic Validator implementation except the {@link #isValid(Object)} - * method. Sub-classes need to implement the {@link #isValid(Object)} method. + * provides a basic Validator implementation except the + * {@link #isValidValue(Object)} method. * <p> * To include the value that failed validation in the exception message you can * use "{0}" in the error message. This will be replaced with the failed value @@ -21,14 +21,20 @@ import com.vaadin.data.Validator; * {@link InvalidValueException#getHtmlMessage()} and throw such exceptions from * {@link #validate(Object)}. * </p> + * <p> + * Since Vaadin 7, subclasses can either implement {@link #validate(Object)} + * directly or implement {@link #isValidValue(Object)} when migrating legacy + * applications. To check validity, {@link #validate(Object)} should be used. + * </p> * + * @param <T> + * The type * @author Vaadin Ltd. * @version * @VERSION@ * @since 5.4 */ -@SuppressWarnings("serial") -public abstract class AbstractValidator implements Validator { +public abstract class AbstractValidator<T> implements Validator { /** * Error message that is included in an {@link InvalidValueException} if @@ -47,14 +53,65 @@ public abstract class AbstractValidator implements Validator { this.errorMessage = errorMessage; } + /** + * Since Vaadin 7, subclasses of AbstractValidator should override + * {@link #isValidValue(Object)} or {@link #validate(Object)} instead of + * {@link #isValid(Object)}. {@link #validate(Object)} should normally be + * used to check values. + * + * @param value + * @return true if the value is valid + */ + public boolean isValid(Object value) { + try { + validate(value); + return true; + } catch (InvalidValueException e) { + return false; + } + } + + /** + * Internally check the validity of a value. This method can be used to + * perform validation in subclasses if customization of the error message is + * not needed. Otherwise, subclasses should override + * {@link #validate(Object)} and the return value of this method is ignored. + * + * This method should not be called from outside the validator class itself. + * + * @param value + * @return + */ + protected abstract boolean isValidValue(T value); + public void validate(Object value) throws InvalidValueException { - if (!isValid(value)) { - String message = getErrorMessage().replace("{0}", String.valueOf(value)); + // isValidType ensures that value can safely be cast to TYPE + if (!isValidType(value) || !isValidValue((T) value)) { + String message = getErrorMessage().replace("{0}", + String.valueOf(value)); throw new InvalidValueException(message); } } /** + * Checks the type of the value to validate to ensure it conforms with + * getType. Enables sub classes to handle the specific type instead of + * Object. + * + * @param value + * The value to check + * @return true if the value can safely be cast to the type specified by + * {@link #getType()} + */ + protected boolean isValidType(Object value) { + if (value == null) { + return true; + } + + return getType().isAssignableFrom(value.getClass()); + } + + /** * Returns the message to be included in the exception in case the value * does not validate. * @@ -76,4 +133,6 @@ public abstract class AbstractValidator implements Validator { public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } + + public abstract Class<T> getType(); } diff --git a/src/com/vaadin/data/validator/BeanValidator.java b/src/com/vaadin/data/validator/BeanValidator.java new file mode 100644 index 0000000000..817df85248 --- /dev/null +++ b/src/com/vaadin/data/validator/BeanValidator.java @@ -0,0 +1,173 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.data.validator; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.MessageInterpolator.Context; +import javax.validation.Validation; +import javax.validation.ValidatorFactory; +import javax.validation.metadata.ConstraintDescriptor; + +import com.vaadin.data.Validator; + +/** + * Vaadin {@link Validator} using the JSR-303 (javax.validation) + * annotation-based bean validation. + * + * The annotations of the fields of the beans are used to determine the + * validation to perform. + * + * Note that a JSR-303 implementation (e.g. Hibernate Validator or Apache Bean + * Validation - formerly agimatec validation) must be present on the project + * classpath when using bean validation. + * + * @since 7.0 + * + * @author Petri Hakala + * @author Henri Sara + */ +public class BeanValidator implements Validator { + + private static final long serialVersionUID = 1L; + private static ValidatorFactory factory; + + private transient javax.validation.Validator javaxBeanValidator; + private String propertyName; + private Class<?> beanClass; + private Locale locale; + + /** + * Simple implementation of a message interpolator context that returns + * fixed values. + */ + protected static class SimpleContext implements Context, Serializable { + + private final Object value; + private final ConstraintDescriptor<?> descriptor; + + /** + * Create a simple immutable message interpolator context. + * + * @param value + * value being validated + * @param descriptor + * ConstraintDescriptor corresponding to the constraint being + * validated + */ + public SimpleContext(Object value, ConstraintDescriptor<?> descriptor) { + this.value = value; + this.descriptor = descriptor; + } + + public ConstraintDescriptor<?> getConstraintDescriptor() { + return descriptor; + } + + public Object getValidatedValue() { + return value; + } + + } + + /** + * Creates a Vaadin {@link Validator} utilizing JSR-303 bean validation. + * + * @param beanClass + * bean class based on which the validation should be performed + * @param propertyName + * property to validate + */ + public BeanValidator(Class<?> beanClass, String propertyName) { + this.beanClass = beanClass; + this.propertyName = propertyName; + locale = Locale.getDefault(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.Validator#validate(java.lang.Object) + */ + public void validate(final Object value) throws InvalidValueException { + Set<?> violations = getJavaxBeanValidator().validateValue(beanClass, + propertyName, value); + if (violations.size() > 0) { + List<String> exceptions = new ArrayList<String>(); + for (Object v : violations) { + final ConstraintViolation<?> violation = (ConstraintViolation<?>) v; + String msg = getJavaxBeanValidatorFactory() + .getMessageInterpolator().interpolate( + violation.getMessageTemplate(), + new SimpleContext(value, violation + .getConstraintDescriptor()), locale); + exceptions.add(msg); + } + StringBuilder b = new StringBuilder(); + for (int i = 0; i < exceptions.size(); i++) { + if (i != 0) { + b.append("<br/>"); + } + b.append(exceptions.get(i)); + } + throw new InvalidValueException(b.toString()); + } + } + + /** + * Sets the locale used for validation error messages. + * + * Revalidation is not automatically triggered by setting the locale. + * + * @param locale + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * Gets the locale used for validation error messages. + * + * @return locale used for validation + */ + public Locale getLocale() { + return locale; + } + + /** + * Returns the underlying JSR-303 bean validator factory used. A factory is + * created using {@link Validation} if necessary. + * + * @return {@link ValidatorFactory} to use + */ + protected static ValidatorFactory getJavaxBeanValidatorFactory() { + if (factory == null) { + factory = Validation.buildDefaultValidatorFactory(); + } + + return factory; + } + + /** + * Returns a shared Validator instance to use. An instance is created using + * the validator factory if necessary and thereafter reused by the + * {@link BeanValidator} instance. + * + * @return the JSR-303 {@link javax.validation.Validator} to use + */ + protected javax.validation.Validator getJavaxBeanValidator() { + if (javaxBeanValidator == null) { + javaxBeanValidator = getJavaxBeanValidatorFactory().getValidator(); + } + + return javaxBeanValidator; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/data/validator/CompositeValidator.java b/src/com/vaadin/data/validator/CompositeValidator.java index 6227a3a2d8..956d773032 100644 --- a/src/com/vaadin/data/validator/CompositeValidator.java +++ b/src/com/vaadin/data/validator/CompositeValidator.java @@ -19,37 +19,44 @@ import com.vaadin.data.Validator; * <code>AND</code> and <code>OR</code>. * * @author Vaadin Ltd. - * @version - * @VERSION@ + * @version @VERSION@ * @since 3.0 */ @SuppressWarnings("serial") -public class CompositeValidator extends AbstractValidator { +public class CompositeValidator implements Validator { - /** - * The validators are combined with <code>AND</code> clause: validity of the - * composite implies validity of the all validators it is composed of must - * be valid. - */ - public static final int MODE_AND = 0; + public enum CombinationMode { + /** + * The validators are combined with <code>AND</code> clause: validity of + * the composite implies validity of the all validators it is composed + * of must be valid. + */ + AND, + /** + * The validators are combined with <code>OR</code> clause: validity of + * the composite implies that some of validators it is composed of must + * be valid. + */ + OR; + } /** - * The validators are combined with <code>OR</code> clause: validity of the - * composite implies that some of validators it is composed of must be - * valid. + * @deprecated from 7.0, use {@link CombinationMode#AND} instead   */ - public static final int MODE_OR = 1; - + @Deprecated + public static final CombinationMode MODE_AND = CombinationMode.AND; /** - * The validators are combined with and clause: validity of the composite - * implies validity of the all validators it is composed of + * @deprecated from 7.0, use {@link CombinationMode#OR} instead   */ - public static final int MODE_DEFAULT = MODE_AND; + @Deprecated + public static final CombinationMode MODE_OR = CombinationMode.OR; + + private String errorMessage; /** * Operation mode. */ - private int mode = MODE_DEFAULT; + private CombinationMode mode = CombinationMode.AND; /** * List of contained validators. @@ -61,14 +68,17 @@ public class CompositeValidator extends AbstractValidator { * message. */ public CompositeValidator() { - super(""); + this(CombinationMode.AND, ""); } /** * Constructs a composite validator in given mode. + * + * @param mode + * @param errorMessage */ - public CompositeValidator(int mode, String errorMessage) { - super(errorMessage); + public CompositeValidator(CombinationMode mode, String errorMessage) { + setErrorMessage(errorMessage); setMode(mode); } @@ -91,16 +101,15 @@ public class CompositeValidator extends AbstractValidator { * @throws Validator.InvalidValueException * if the value is not valid. */ - @Override public void validate(Object value) throws Validator.InvalidValueException { switch (mode) { - case MODE_AND: + case AND: for (Validator validator : validators) { validator.validate(value); } return; - case MODE_OR: + case OR: Validator.InvalidValueException first = null; for (Validator v : validators) { try { @@ -122,65 +131,32 @@ public class CompositeValidator extends AbstractValidator { throw first; } } - throw new IllegalStateException( - "The validator is in unsupported operation mode"); - } - - /** - * Checks the validity of the the given value. The value is valid, if: - * <ul> - * <li><code>MODE_AND</code>: All of the sub-validators are valid - * <li><code>MODE_OR</code>: Any of the sub-validators are valid - * </ul> - * - * @param value - * the value to check. - */ - public boolean isValid(Object value) { - switch (mode) { - case MODE_AND: - for (Validator v : validators) { - if (!v.isValid(value)) { - return false; - } - } - return true; - - case MODE_OR: - for (Validator v : validators) { - if (v.isValid(value)) { - return true; - } - } - return false; - } - throw new IllegalStateException( - "The valitor is in unsupported operation mode"); } /** * Gets the mode of the validator. * - * @return Operation mode of the validator: <code>MODE_AND</code> or - * <code>MODE_OR</code>. + * @return Operation mode of the validator: {@link CombinationMode#AND} or + * {@link CombinationMode#OR}. */ - public final int getMode() { + public final CombinationMode getMode() { return mode; } /** * Sets the mode of the validator. The valid modes are: * <ul> - * <li><code>MODE_AND</code> (default) - * <li><code>MODE_OR</code> + * <li>{@link CombinationMode#AND} (default) + * <li>{@link CombinationMode#OR} * </ul> * * @param mode * the mode to set. */ - public void setMode(int mode) { - if (mode != MODE_AND && mode != MODE_OR) { - throw new IllegalArgumentException("Mode " + mode + " unsupported"); + public void setMode(CombinationMode mode) { + if (mode == null) { + throw new IllegalArgumentException( + "The validator can't be set to null"); } this.mode = mode; } @@ -189,10 +165,9 @@ public class CompositeValidator extends AbstractValidator { * Gets the error message for the composite validator. If the error message * is null, original error messages of the sub-validators are used instead. */ - @Override public String getErrorMessage() { - if (super.getErrorMessage() != null) { - return super.getErrorMessage(); + if (errorMessage != null) { + return errorMessage; } // TODO Return composite error message @@ -240,11 +215,14 @@ public class CompositeValidator extends AbstractValidator { * validators of given type null is returned. * </p> * + * @param validatorType + * The type of validators to return + * * @return Collection<Validator> of validators compatible with given type - * that must apply or null if none fould. + * that must apply or null if none found. */ public Collection<Validator> getSubValidators(Class validatorType) { - if (mode != MODE_AND) { + if (mode != CombinationMode.AND) { return null; } @@ -266,4 +244,15 @@ public class CompositeValidator extends AbstractValidator { return found.isEmpty() ? null : found; } + /** + * Sets the message to be included in the exception in case the value does + * not validate. The exception message is typically shown to the end user. + * + * @param errorMessage + * the error message. + */ + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + } diff --git a/src/com/vaadin/data/validator/DateRangeValidator.java b/src/com/vaadin/data/validator/DateRangeValidator.java new file mode 100644 index 0000000000..24f3d3ce10 --- /dev/null +++ b/src/com/vaadin/data/validator/DateRangeValidator.java @@ -0,0 +1,51 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.validator; + +import java.util.Date; + +import com.vaadin.ui.DateField.Resolution; + +/** + * Validator for validating that a Date is inside a given range. + * + * <p> + * Note that the comparison is done directly on the Date object so take care + * that the hours/minutes/seconds/milliseconds of the min/max values are + * properly set. + * </p> + * + * @author Vaadin Ltd. + * @version + * @VERSION@ + * @since 7.0 + */ +public class DateRangeValidator extends RangeValidator<Date> { + + /** + * Creates a validator for checking that an Date is within a given range. + * <p> + * By default the range is inclusive i.e. both minValue and maxValue are + * valid values. Use {@link #setMinValueIncluded(boolean)} or + * {@link #setMaxValueIncluded(boolean)} to change it. + * </p> + * <p> + * Note that the comparison is done directly on the Date object so take care + * that the hours/minutes/seconds/milliseconds of the min/max values are + * properly set. + * </p> + * + * @param errorMessage + * the message to display in case the value does not validate. + * @param minValue + * The minimum value to accept or null for no limit + * @param maxValue + * The maximum value to accept or null for no limit + */ + public DateRangeValidator(String errorMessage, Date minValue, + Date maxValue, Resolution resolution) { + super(errorMessage, Date.class, minValue, maxValue); + } + +} diff --git a/src/com/vaadin/data/validator/DoubleRangeValidator.java b/src/com/vaadin/data/validator/DoubleRangeValidator.java new file mode 100644 index 0000000000..05ae2f827e --- /dev/null +++ b/src/com/vaadin/data/validator/DoubleRangeValidator.java @@ -0,0 +1,37 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.validator; + +/** + * Validator for validating that a {@link Double} is inside a given range. + * + * @author Vaadin Ltd. + * @version + * @VERSION@ + * @since 7.0 + */ +@SuppressWarnings("serial") +public class DoubleRangeValidator extends RangeValidator<Double> { + + /** + * Creates a validator for checking that an Double is within a given range. + * + * By default the range is inclusive i.e. both minValue and maxValue are + * valid values. Use {@link #setMinValueIncluded(boolean)} or + * {@link #setMaxValueIncluded(boolean)} to change it. + * + * + * @param errorMessage + * the message to display in case the value does not validate. + * @param minValue + * The minimum value to accept or null for no limit + * @param maxValue + * The maximum value to accept or null for no limit + */ + public DoubleRangeValidator(String errorMessage, Double minValue, + Double maxValue) { + super(errorMessage, Double.class, minValue, maxValue); + } + +} diff --git a/src/com/vaadin/data/validator/DoubleValidator.java b/src/com/vaadin/data/validator/DoubleValidator.java index e90919c17d..18f1909add 100644 --- a/src/com/vaadin/data/validator/DoubleValidator.java +++ b/src/com/vaadin/data/validator/DoubleValidator.java @@ -12,7 +12,9 @@ package com.vaadin.data.validator; * @version * @VERSION@ * @since 5.4 + * @deprecated in Vaadin 7.0. Use an Double converter on the field instead. */ +@Deprecated @SuppressWarnings("serial") public class DoubleValidator extends AbstractStringValidator { @@ -22,13 +24,17 @@ public class DoubleValidator extends AbstractStringValidator { * * @param errorMessage * the message to display in case the value does not validate. + * @deprecated in Vaadin 7.0. Use a Double converter on the field instead + * and/or use a {@link DoubleRangeValidator} for validating that + * the value is inside a given range. */ + @Deprecated public DoubleValidator(String errorMessage) { super(errorMessage); } @Override - protected boolean isValidString(String value) { + protected boolean isValidValue(String value) { try { Double.parseDouble(value); return true; @@ -37,4 +43,16 @@ public class DoubleValidator extends AbstractStringValidator { } } + @Override + public void validate(Object value) throws InvalidValueException { + if (value != null && value instanceof Double) { + // Allow Doubles to pass through the validator for easier + // migration. Otherwise a TextField connected to an double property + // with a DoubleValidator will fail. + return; + } + + super.validate(value); + } + } diff --git a/src/com/vaadin/data/validator/IntegerRangeValidator.java b/src/com/vaadin/data/validator/IntegerRangeValidator.java new file mode 100644 index 0000000000..c171dd97d8 --- /dev/null +++ b/src/com/vaadin/data/validator/IntegerRangeValidator.java @@ -0,0 +1,37 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.validator; + +/** + * Validator for validating that an {@link Integer} is inside a given range. + * + * @author Vaadin Ltd. + * @version + * @VERSION@ + * @since 5.4 + */ +@SuppressWarnings("serial") +public class IntegerRangeValidator extends RangeValidator<Integer> { + + /** + * Creates a validator for checking that an Integer is within a given range. + * + * By default the range is inclusive i.e. both minValue and maxValue are + * valid values. Use {@link #setMinValueIncluded(boolean)} or + * {@link #setMaxValueIncluded(boolean)} to change it. + * + * + * @param errorMessage + * the message to display in case the value does not validate. + * @param minValue + * The minimum value to accept or null for no limit + * @param maxValue + * The maximum value to accept or null for no limit + */ + public IntegerRangeValidator(String errorMessage, Integer minValue, + Integer maxValue) { + super(errorMessage, Integer.class, minValue, maxValue); + } + +} diff --git a/src/com/vaadin/data/validator/IntegerValidator.java b/src/com/vaadin/data/validator/IntegerValidator.java index 50b45b90ce..88ae9f3f0b 100644 --- a/src/com/vaadin/data/validator/IntegerValidator.java +++ b/src/com/vaadin/data/validator/IntegerValidator.java @@ -12,8 +12,10 @@ package com.vaadin.data.validator; * @version * @VERSION@ * @since 5.4 + * @deprecated in Vaadin 7.0. Use an Integer converter on the field instead. */ @SuppressWarnings("serial") +@Deprecated public class IntegerValidator extends AbstractStringValidator { /** @@ -22,14 +24,18 @@ public class IntegerValidator extends AbstractStringValidator { * * @param errorMessage * the message to display in case the value does not validate. + * @deprecated in Vaadin 7.0. Use an Integer converter on the field instead + * and/or use an {@link IntegerRangeValidator} for validating + * that the value is inside a given range. */ + @Deprecated public IntegerValidator(String errorMessage) { super(errorMessage); } @Override - protected boolean isValidString(String value) { + protected boolean isValidValue(String value) { try { Integer.parseInt(value); return true; @@ -38,4 +44,15 @@ public class IntegerValidator extends AbstractStringValidator { } } + @Override + public void validate(Object value) throws InvalidValueException { + if (value != null && value instanceof Integer) { + // Allow Integers to pass through the validator for easier + // migration. Otherwise a TextField connected to an integer property + // with an IntegerValidator will fail. + return; + } + + super.validate(value); + } } diff --git a/src/com/vaadin/data/validator/NullValidator.java b/src/com/vaadin/data/validator/NullValidator.java index 77f9a606b4..62b2580d48 100644 --- a/src/com/vaadin/data/validator/NullValidator.java +++ b/src/com/vaadin/data/validator/NullValidator.java @@ -51,17 +51,6 @@ public class NullValidator implements Validator { } /** - * Tests if the given value is valid. - * - * @param value - * the value to validate. - * @returns <code>true</code> for valid value, otherwise <code>false</code>. - */ - public boolean isValid(Object value) { - return onlyNullAllowed ? value == null : value != null; - } - - /** * Returns <code>true</code> if nulls are allowed otherwise * <code>false</code>. */ diff --git a/src/com/vaadin/data/validator/RangeValidator.java b/src/com/vaadin/data/validator/RangeValidator.java new file mode 100644 index 0000000000..433271274f --- /dev/null +++ b/src/com/vaadin/data/validator/RangeValidator.java @@ -0,0 +1,186 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.validator; + +/** + * An base implementation for validating any objects that implement + * {@link Comparable}. + * + * Verifies that the value is of the given type and within the (optionally) + * given limits. Typically you want to use a sub class of this like + * {@link IntegerRangeValidator}, {@link DoubleRangeValidator} or + * {@link DateRangeValidator} in applications. + * <p> + * Note that {@link RangeValidator} always accept null values. Make a field + * required to ensure that no empty values are accepted or override + * {@link #isValidValue(Comparable)}. + * </p> + * + * @param <T> + * The type of Number to validate. Must implement Comparable so that + * minimum and maximum checks work. + * @author Vaadin Ltd. + * @version + * @VERSION@ + * @since 7.0 + */ +public class RangeValidator<T extends Comparable> extends AbstractValidator<T> { + + private T minValue = null; + private boolean minValueIncluded = true; + private T maxValue = null; + private boolean maxValueIncluded = true; + private Class<T> type; + + /** + * Creates a new range validator of the given type. + * + * @param errorMessage + * The error message to use if validation fails + * @param type + * The type of object the validator can validate. + * @param minValue + * The minimum value that should be accepted or null for no limit + * @param maxValue + * The maximum value that should be accepted or null for no limit + */ + public RangeValidator(String errorMessage, Class<T> type, T minValue, + T maxValue) { + super(errorMessage); + this.type = type; + this.minValue = minValue; + this.maxValue = maxValue; + } + + /** + * Checks if the minimum value is part of the accepted range + * + * @return true if the minimum value is part of the range, false otherwise + */ + public boolean isMinValueIncluded() { + return minValueIncluded; + } + + /** + * Sets if the minimum value is part of the accepted range + * + * @param minValueIncluded + * true if the minimum value should be part of the range, false + * otherwise + */ + public void setMinValueIncluded(boolean minValueIncluded) { + this.minValueIncluded = minValueIncluded; + } + + /** + * Checks if the maximum value is part of the accepted range + * + * @return true if the maximum value is part of the range, false otherwise + */ + public boolean isMaxValueIncluded() { + return maxValueIncluded; + } + + /** + * Sets if the maximum value is part of the accepted range + * + * @param maxValueIncluded + * true if the maximum value should be part of the range, false + * otherwise + */ + public void setMaxValueIncluded(boolean maxValueIncluded) { + this.maxValueIncluded = maxValueIncluded; + } + + /** + * Gets the minimum value of the range + * + * @return the minimum value + */ + public T getMinValue() { + return minValue; + } + + /** + * Sets the minimum value of the range. Use + * {@link #setMinValueIncluded(boolean)} to control whether this value is + * part of the range or not. + * + * @param minValue + * the minimum value + */ + public void setMinValue(T minValue) { + this.minValue = minValue; + } + + /** + * Gets the maximum value of the range + * + * @return the maximum value + */ + public T getMaxValue() { + return maxValue; + } + + /** + * Sets the maximum value of the range. Use + * {@link #setMaxValueIncluded(boolean)} to control whether this value is + * part of the range or not. + * + * @param maxValue + * the maximum value + */ + public void setMaxValue(T maxValue) { + this.maxValue = maxValue; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.validator.AbstractValidator#isValidValue(java.lang.Object + * ) + */ + @Override + protected boolean isValidValue(T value) { + if (value == null) { + return true; + } + + if (getMinValue() != null) { + // Ensure that the min limit is ok + int result = value.compareTo(getMinValue()); + if (result < 0) { + // value less than min value + return false; + } else if (result == 0 && !isMinValueIncluded()) { + // values equal and min value not included + return false; + } + } + if (getMaxValue() != null) { + // Ensure that the Max limit is ok + int result = value.compareTo(getMaxValue()); + if (result > 0) { + // value greater than max value + return false; + } else if (result == 0 && !isMaxValueIncluded()) { + // values equal and max value not included + return false; + } + } + return true; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.validator.AbstractValidator#getType() + */ + @Override + public Class<T> getType() { + return type; + } + +} diff --git a/src/com/vaadin/data/validator/RegexpValidator.java b/src/com/vaadin/data/validator/RegexpValidator.java index 684de7697c..8143d54c97 100644 --- a/src/com/vaadin/data/validator/RegexpValidator.java +++ b/src/com/vaadin/data/validator/RegexpValidator.java @@ -62,8 +62,15 @@ public class RegexpValidator extends AbstractStringValidator { this.complete = complete; } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.validator.AbstractValidator#isValidValue(java.lang.Object + * ) + */ @Override - protected boolean isValidString(String value) { + protected boolean isValidValue(String value) { if (complete) { return getMatcher(value).matches(); } else { diff --git a/src/com/vaadin/data/validator/StringLengthValidator.java b/src/com/vaadin/data/validator/StringLengthValidator.java index d7edea216d..54b2d28f58 100644 --- a/src/com/vaadin/data/validator/StringLengthValidator.java +++ b/src/com/vaadin/data/validator/StringLengthValidator.java @@ -14,11 +14,11 @@ package com.vaadin.data.validator; * @since 3.0 */ @SuppressWarnings("serial") -public class StringLengthValidator extends AbstractValidator { +public class StringLengthValidator extends AbstractStringValidator { - private int minLength = -1; + private Integer minLength = null; - private int maxLength = -1; + private Integer maxLength = null; private boolean allowNull = true; @@ -33,21 +33,25 @@ public class StringLengthValidator extends AbstractValidator { } /** - * Creates a new StringLengthValidator with a given error message, - * permissable lengths and null-string allowance. + * Creates a new StringLengthValidator with a given error message and + * minimum and maximum length limits. * * @param errorMessage * the message to display in case the value does not validate. * @param minLength - * the minimum permissible length of the string. + * the minimum permissible length of the string or null for no + * limit. A negative value for no limit is also supported for + * backwards compatibility. * @param maxLength - * the maximum permissible length of the string. + * the maximum permissible length of the string or null for no + * limit. A negative value for no limit is also supported for + * backwards compatibility. * @param allowNull * Are null strings permissible? This can be handled better by * setting a field as required or not. */ - public StringLengthValidator(String errorMessage, int minLength, - int maxLength, boolean allowNull) { + public StringLengthValidator(String errorMessage, Integer minLength, + Integer maxLength, boolean allowNull) { this(errorMessage); setMinLength(minLength); setMaxLength(maxLength); @@ -61,17 +65,14 @@ public class StringLengthValidator extends AbstractValidator { * the value to validate. * @return <code>true</code> for valid value, otherwise <code>false</code>. */ - public boolean isValid(Object value) { + @Override + protected boolean isValidValue(String value) { if (value == null) { return allowNull; } - final String s = value.toString(); - if (s == null) { - return allowNull; - } - final int len = s.length(); - if ((minLength >= 0 && len < minLength) - || (maxLength >= 0 && len > maxLength)) { + final int len = value.length(); + if ((minLength != null && minLength > -1 && len < minLength) + || (maxLength != null && maxLength > -1 && len > maxLength)) { return false; } return true; @@ -91,18 +92,18 @@ public class StringLengthValidator extends AbstractValidator { /** * Gets the maximum permissible length of the string. * - * @return the maximum length of the string. + * @return the maximum length of the string or null if there is no limit */ - public final int getMaxLength() { + public Integer getMaxLength() { return maxLength; } /** * Gets the minimum permissible length of the string. * - * @return the minimum length of the string. + * @return the minimum length of the string or null if there is no limit */ - public final int getMinLength() { + public Integer getMinLength() { return minLength; } @@ -119,12 +120,9 @@ public class StringLengthValidator extends AbstractValidator { * Sets the maximum permissible length of the string. * * @param maxLength - * the length to set. + * the maximum length to accept or null for no limit */ - public void setMaxLength(int maxLength) { - if (maxLength < -1) { - maxLength = -1; - } + public void setMaxLength(Integer maxLength) { this.maxLength = maxLength; } @@ -132,12 +130,9 @@ public class StringLengthValidator extends AbstractValidator { * Sets the minimum permissible length. * * @param minLength - * the length to set. + * the minimum length to accept or null for no limit */ - public void setMinLength(int minLength) { - if (minLength < -1) { - minLength = -1; - } + public void setMinLength(Integer minLength) { this.minLength = minLength; } diff --git a/src/com/vaadin/event/ActionManager.java b/src/com/vaadin/event/ActionManager.java index 13496c1deb..08e9c85043 100644 --- a/src/com/vaadin/event/ActionManager.java +++ b/src/com/vaadin/event/ActionManager.java @@ -11,6 +11,7 @@ import com.vaadin.event.Action.Handler; import com.vaadin.terminal.KeyMapper; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.VariableOwner; import com.vaadin.ui.Component; /** @@ -37,7 +38,7 @@ public class ActionManager implements Action.Container, Action.Handler, protected HashSet<Handler> actionHandlers = null; /** Action mapper */ - protected KeyMapper actionMapper = null; + protected KeyMapper<Action> actionMapper = null; protected Component viewer; @@ -47,7 +48,8 @@ public class ActionManager implements Action.Container, Action.Handler, } - public <T extends Component & Container> ActionManager(T viewer) { + public <T extends Component & Container & VariableOwner> ActionManager( + T viewer) { this.viewer = viewer; } @@ -57,7 +59,8 @@ public class ActionManager implements Action.Container, Action.Handler, } } - public <T extends Component & Container> void setViewer(T viewer) { + public <T extends Component & Container & VariableOwner> void setViewer( + T viewer) { if (viewer == this.viewer) { return; } @@ -151,9 +154,9 @@ public class ActionManager implements Action.Container, Action.Handler, * removed but still exist on client side */ if (!actions.isEmpty() || clientHasActions) { - actionMapper = new KeyMapper(); + actionMapper = new KeyMapper<Action>(); - paintTarget.addVariable(viewer, "action", ""); + paintTarget.addVariable((VariableOwner) viewer, "action", ""); paintTarget.startTag("actions"); for (final Action a : actions) { @@ -195,7 +198,7 @@ public class ActionManager implements Action.Container, Action.Handler, public void handleActions(Map<String, Object> variables, Container sender) { if (variables.containsKey("action") && actionMapper != null) { final String key = (String) variables.get("action"); - final Action action = (Action) actionMapper.get(key); + final Action action = actionMapper.get(key); final Object target = variables.get("actiontarget"); if (action != null) { handleAction(action, sender, target); diff --git a/src/com/vaadin/event/FieldEvents.java b/src/com/vaadin/event/FieldEvents.java index 28fd6bb4f7..20e9fabb36 100644 --- a/src/com/vaadin/event/FieldEvents.java +++ b/src/com/vaadin/event/FieldEvents.java @@ -8,8 +8,10 @@ import java.io.Serializable; import java.lang.reflect.Method; import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; import com.vaadin.tools.ReflectTools; import com.vaadin.ui.Component; +import com.vaadin.ui.Component.Event; import com.vaadin.ui.Field; import com.vaadin.ui.Field.ValueChangeEvent; import com.vaadin.ui.TextField; @@ -247,4 +249,25 @@ public interface FieldEvents { public void removeListener(TextChangeListener listener); } + + public static abstract class FocusAndBlurServerRpcImpl implements + FocusAndBlurServerRpc { + + private Component component; + + public FocusAndBlurServerRpcImpl(Component component) { + this.component = component; + } + + protected abstract void fireEvent(Event event); + + public void blur() { + fireEvent(new BlurEvent(component)); + } + + public void focus() { + fireEvent(new FocusEvent(component)); + } + }; + } diff --git a/src/com/vaadin/event/ItemClickEvent.java b/src/com/vaadin/event/ItemClickEvent.java index c503f8f9c9..bb41398e8d 100644 --- a/src/com/vaadin/event/ItemClickEvent.java +++ b/src/com/vaadin/event/ItemClickEvent.java @@ -6,7 +6,6 @@ package com.vaadin.event; import java.io.Serializable; import java.lang.reflect.Method; -import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.event.MouseEvents.ClickEvent; @@ -84,19 +83,6 @@ public class ItemClickEvent extends ClickEvent implements Serializable { } /** - * Components implementing - * - * @link {@link Container} interface may support emitting - * {@link ItemClickEvent}s. - * - * @deprecated Use {@link ItemClickNotifier} instead. ItemClickSource was - * deprecated in version 6.5. - */ - @Deprecated - public interface ItemClickSource extends ItemClickNotifier { - } - - /** * The interface for adding and removing <code>ItemClickEvent</code> * listeners. By implementing this interface a class explicitly announces * that it will generate an <code>ItemClickEvent</code> when one of its diff --git a/src/com/vaadin/event/LayoutEvents.java b/src/com/vaadin/event/LayoutEvents.java index 2dda15fbf5..960fff00c0 100644 --- a/src/com/vaadin/event/LayoutEvents.java +++ b/src/com/vaadin/event/LayoutEvents.java @@ -7,9 +7,11 @@ import java.io.Serializable; import java.lang.reflect.Method; import com.vaadin.event.MouseEvents.ClickEvent; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.tools.ReflectTools; import com.vaadin.ui.Component; +import com.vaadin.ui.ComponentContainer; public interface LayoutEvents { @@ -120,5 +122,17 @@ public interface LayoutEvents { return childComponent; } + public static LayoutClickEvent createEvent(ComponentContainer layout, + MouseEventDetails mouseDetails, Connector clickedConnector) { + Component clickedComponent = (Component) clickedConnector; + Component childComponent = clickedComponent; + while (childComponent != null + && childComponent.getParent() != layout) { + childComponent = childComponent.getParent(); + } + + return new LayoutClickEvent(layout, mouseDetails, clickedComponent, + childComponent); + } } }
\ No newline at end of file diff --git a/src/com/vaadin/event/dd/acceptcriteria/ClientCriterion.java b/src/com/vaadin/event/dd/acceptcriteria/ClientCriterion.java index 920112360e..8b64106be7 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/ClientCriterion.java +++ b/src/com/vaadin/event/dd/acceptcriteria/ClientCriterion.java @@ -9,13 +9,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterion; -import com.vaadin.ui.ClientWidget; /** * An annotation type used to point the client side counterpart for server side - * a {@link AcceptCriterion} class. Usage is pretty similar to - * {@link ClientWidget} which is used with Vaadin components that have a - * specialized client side counterpart. + * a {@link AcceptCriterion} class. * <p> * Annotations are used at GWT compilation phase, so remember to rebuild your * widgetset if you do changes for {@link ClientCriterion} mappings. diff --git a/src/com/vaadin/external/json/JSONArray.java b/src/com/vaadin/external/json/JSONArray.java new file mode 100644 index 0000000000..2307749ffc --- /dev/null +++ b/src/com/vaadin/external/json/JSONArray.java @@ -0,0 +1,963 @@ +package com.vaadin.external.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.Serializable; +import java.io.Writer; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a + * string wrapped in square brackets with commas separating the values. The + * internal form is an object having <code>get</code> and <code>opt</code> + * methods for accessing the values by index, and <code>put</code> methods for + * adding or replacing values. The values can be any of these types: + * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>, + * <code>Number</code>, <code>String</code>, or the + * <code>JSONObject.NULL object</code>. + * <p> + * The constructor can convert a JSON text into a Java object. The + * <code>toString</code> method converts to JSON text. + * <p> + * A <code>get</code> method returns a value if one can be found, and throws an + * exception if one cannot be found. An <code>opt</code> method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + * <p> + * The generic <code>get()</code> and <code>opt()</code> methods return an + * object which you can cast or query for type. There are also typed + * <code>get</code> and <code>opt</code> methods that do type checking and type + * coercion for you. + * <p> + * The texts produced by the <code>toString</code> methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + * <ul> + * <li>An extra <code>,</code> <small>(comma)</small> may appear just + * before the closing bracket.</li> + * <li>The <code>null</code> value will be inserted when there is <code>,</code> + * <small>(comma)</small> elision.</li> + * <li>Strings may be quoted with <code>'</code> <small>(single + * quote)</small>.</li> + * <li>Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and + * if they are not the reserved words <code>true</code>, <code>false</code>, or + * <code>null</code>.</li> + * <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as + * well as by <code>,</code> <small>(comma)</small>.</li> + * <li>Numbers may have the <code>0x-</code> <small>(hex)</small> prefix.</li> + * </ul> + * + * @author JSON.org + * @version 2011-08-25 + */ +public class JSONArray implements Serializable { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private ArrayList myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + myArrayList = new ArrayList(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x + * A JSONTokener + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + myArrayList.add(JSONObject.NULL); + } else { + x.back(); + myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source + * A string that begins with <code>[</code> <small>(left + * bracket)</small> and ends with <code>]</code> + * <small>(right bracket)</small>. + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection + * A Collection. + */ + public JSONArray(Collection collection) { + myArrayList = new ArrayList(); + if (collection != null) { + Iterator iter = collection.iterator(); + while (iter.hasNext()) { + myArrayList.add(JSONObject.wrap(iter.next())); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @throws JSONException + * If not an array. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException( + "JSONArray initial value should be a string or collection or array."); + } + } + + /** + * Get the object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException + * If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" + * and "false" are converted to boolean. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException + * If there is no value for the index or if the value is not + * convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = get(index); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the int value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value is not a number. + */ + public int getInt(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException + * If there is no value for the index. or if the value is not a + * JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index + * subscript + * @return A JSONObject value. + * @throws JSONException + * If there is no value for the index or if the value is not a + * JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public long getLong(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the string associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException + * If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index + * The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The + * <code>separator</code> string is inserted between each element. Warning: + * This method assumes that the data structure is acyclical. + * + * @param separator + * A string that will be inserted between the elements. + * @return a string. + * @throws JSONException + * If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = length(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= length()) ? null : myArrayList.get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false + * if there is no value at that index, or if the value is not Boolean.TRUE + * or the String "true". + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the + * defaultValue if there is no value at that index or if it is not a Boolean + * or the String "true" or "false" (case insensitive). + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * subscript + * @param defaultValue + * The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return getDouble(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional int value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return getInt(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index + * subscript + * @return A JSONArray value, or null if the index has no value, or if the + * value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if + * the key is not found, or null if the index has no value, or if the value + * is not a JSONObject. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return getLong(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is coverted to a string. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = opt(index); + return JSONObject.NULL.equals(object) ? object.toString() + : defaultValue; + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value + * A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value + * A Collection value. + * @return this. + */ + public JSONArray put(Collection value) { + put(new JSONArray(value)); + return this; + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value + * A double value. + * @throws JSONException + * if the value is not finite. + * @return this. + */ + public JSONArray put(double value) throws JSONException { + Double d = new Double(value); + JSONObject.testValidity(d); + put(d); + return this; + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value + * An int value. + * @return this. + */ + public JSONArray put(int value) { + put(new Integer(value)); + return this; + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value + * A long value. + * @return this. + */ + public JSONArray put(long value) { + put(new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value + * A Map value. + * @return this. + */ + public JSONArray put(Map value) { + put(new JSONObject(value)); + return this; + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value + * An object value. The value should be a Boolean, Double, + * Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + */ + public JSONArray put(Object value) { + myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * A boolean value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index + * The subscript. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + put(index, new JSONArray(value)); + return this; + } + + /** + * Put or replace a double value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A double value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, double value) throws JSONException { + put(index, new Double(value)); + return this; + } + + /** + * Put or replace an int value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * An int value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + put(index, new Integer(value)); + return this; + } + + /** + * Put or replace a long value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A long value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + put(index, new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index + * The subscript. + * @param value + * The Map value. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Map value) throws JSONException { + put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * The value to put into the array. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < length()) { + myArrayList.set(index, value); + } else { + while (index != length()) { + put(JSONObject.NULL); + } + put(value); + } + return this; + } + + /** + * Remove an index and close the hole. + * + * @param index + * The index of the element to be removed. + * @return The value that was associated with the index, or null if there + * was no value. + */ + public Object remove(int index) { + Object o = opt(index); + myArrayList.remove(index); + return o; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names + * A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException + * If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.length() == 0 || length() == 0) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary + * whitespace is added. If it is not possible to produce a syntactically + * correct JSON text then null will be returned instead. This could occur if + * the array contains an invalid number. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable representation of the + * array. + */ + @Override + public String toString() { + try { + return '[' + join(",") + ']'; + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method + * assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, transmittable representation of the + * object, beginning with <code>[</code> <small>(left + * bracket)</small> and ending with <code>]</code> + * <small>(right bracket)</small>. + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + return toString(indentFactor, 0); + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method + * assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indention of the top level. + * @return a printable, displayable, transmittable representation of the + * array. + * @throws JSONException + */ + String toString(int indentFactor, int indent) throws JSONException { + int len = length(); + if (len == 0) { + return "[]"; + } + int i; + StringBuffer sb = new StringBuffer("["); + if (len == 1) { + sb.append(JSONObject.valueToString(myArrayList.get(0), + indentFactor, indent)); + } else { + int newindent = indent + indentFactor; + sb.append('\n'); + for (i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(",\n"); + } + for (int j = 0; j < newindent; j += 1) { + sb.append(' '); + } + sb.append(JSONObject.valueToString(myArrayList.get(i), + indentFactor, newindent)); + } + sb.append('\n'); + for (i = 0; i < indent; i += 1) { + sb.append(' '); + } + } + sb.append(']'); + return sb.toString(); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + try { + boolean b = false; + int len = length(); + + writer.write('['); + + for (int i = 0; i < len; i += 1) { + if (b) { + writer.write(','); + } + Object v = myArrayList.get(i); + if (v instanceof JSONObject) { + ((JSONObject) v).write(writer); + } else if (v instanceof JSONArray) { + ((JSONArray) v).write(writer); + } else { + writer.write(JSONObject.valueToString(v)); + } + b = true; + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +}
\ No newline at end of file diff --git a/src/com/vaadin/external/json/JSONException.java b/src/com/vaadin/external/json/JSONException.java new file mode 100644 index 0000000000..fecc38974e --- /dev/null +++ b/src/com/vaadin/external/json/JSONException.java @@ -0,0 +1,31 @@ +package com.vaadin.external.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2010-12-24 + */ +public class JSONException extends Exception { + private static final long serialVersionUID = 0; + private Throwable cause; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message + * Detail about the reason for the exception. + */ + public JSONException(String message) { + super(message); + } + + public JSONException(Throwable cause) { + super(cause.getMessage()); + this.cause = cause; + } + + public Throwable getCause() { + return this.cause; + } +} diff --git a/src/com/vaadin/external/json/JSONObject.java b/src/com/vaadin/external/json/JSONObject.java new file mode 100644 index 0000000000..ba772933be --- /dev/null +++ b/src/com/vaadin/external/json/JSONObject.java @@ -0,0 +1,1693 @@ +package com.vaadin.external.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.Serializable; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external + * form is a string wrapped in curly braces with colons between the names and + * values, and commas between the values and names. The internal form is an + * object having <code>get</code> and <code>opt</code> methods for accessing the + * values by name, and <code>put</code> methods for adding or replacing values + * by name. The values can be any of these types: <code>Boolean</code>, + * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>, + * <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject + * constructor can be used to convert an external form JSON text into an + * internal form whose values can be retrieved with the <code>get</code> and + * <code>opt</code> methods, or to convert values into a JSON text using the + * <code>put</code> and <code>toString</code> methods. A <code>get</code> method + * returns a value if one can be found, and throws an exception if one cannot be + * found. An <code>opt</code> method returns a default value instead of throwing + * an exception, and so is useful for obtaining optional values. + * <p> + * The generic <code>get()</code> and <code>opt()</code> methods return an + * object, which you can cast or query for type. There are also typed + * <code>get</code> and <code>opt</code> methods that do type checking and type + * coercion for you. The opt methods differ from the get methods in that they do + * not throw. Instead, they return a specified value, such as null. + * <p> + * The <code>put</code> methods add or replace values in an object. For example, + * + * <pre> + * myString = new JSONObject().put("JSON", "Hello, World!").toString(); + * </pre> + * + * produces the string <code>{"JSON": "Hello, World"}</code>. + * <p> + * The texts produced by the <code>toString</code> methods strictly conform to + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: + * <ul> + * <li>An extra <code>,</code> <small>(comma)</small> may appear just + * before the closing brace.</li> + * <li>Strings may be quoted with <code>'</code> <small>(single + * quote)</small>.</li> + * <li>Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and + * if they are not the reserved words <code>true</code>, <code>false</code>, or + * <code>null</code>.</li> + * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as by + * <code>:</code>.</li> + * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as + * well as by <code>,</code> <small>(comma)</small>.</li> + * <li>Numbers may have the <code>0x-</code> <small>(hex)</small> prefix.</li> + * </ul> + * + * @author JSON.org + * @version 2011-10-16 + */ +public class JSONObject implements Serializable { + + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null implements Serializable { + + /** + * There is only intended to be a single instance of the NULL object, so + * the clone method returns itself. + * + * @return NULL. + */ + @Override + protected final Object clone() { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object + * An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or + * null. + */ + @Override + public boolean equals(Object object) { + return object == null || object == this; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override + public String toString() { + return "null"; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * <code>NULL</code> object than to use Java's <code>null</code> value. + * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>. + * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>. + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of + * strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo + * A JSONObject. + * @param names + * An array of strings. + * @throws JSONException + * @exception JSONException + * If a value is a non-finite number or if a name is + * duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (int i = 0; i < names.length; i += 1) { + try { + putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. We will also tolerate '=' or '=>'. + + c = x.nextClean(); + if (c == '=') { + if (x.next() != '>') { + x.back(); + } + } else if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + putOnce(key, x.nextValue()); + + // Pairs are separated by ','. We will also tolerate ';'. + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param map + * A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + */ + public JSONObject(Map map) { + this.map = new HashMap(); + if (map != null) { + Iterator i = map.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = (Map.Entry) i.next(); + Object value = e.getValue(); + if (value != null) { + this.map.put(e.getKey(), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with <code>"get"</code> or + * <code>"is"</code> followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the + * new JSONObject. + * + * The key is formed by removing the <code>"get"</code> or <code>"is"</code> + * prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + * + * For example, if an object has a method named <code>"getName"</code>, and + * if the result of calling <code>object.getName()</code> is + * <code>"Larry Fine"</code>, then the JSONObject will contain + * <code>"name": "Larry Fine"</code>. + * + * @param bean + * An object that has getter methods that should be used to make + * a JSONObject. + */ + public JSONObject(Object bean) { + this(); + populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings from + * the names array, and the values will be the field values associated with + * those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object + * An object that has fields that should be used to make a + * JSONObject. + * @param names + * An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source + * A string beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> + * <small>(right brace)</small>. + * @exception JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName + * The ResourceBundle base name. + * @param locale + * The Locale to load the ResourceBundle for. + * @throws JSONException + * If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, + Thread.currentThread().getContextClassLoader()); + + // Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key instanceof String) { + + // Go through the path, ensuring that there is a nested + // JSONObject for each + // segment except the last. Add the value using the last + // segment's name into + // the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a JSONArray + * is stored under the key to hold all of the accumulated values. If there + * is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is an invalid number or if the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = opt(key); + if (object == null) { + put(key, value instanceof JSONArray ? new JSONArray().put(value) + : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the key is null or if the current value associated with + * the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = opt(key); + if (object == null) { + put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + + "] is not a JSONArray."); + } + return this; + } + + /** + * Produce a string from a double. The string "null" will be returned if the + * number is not finite. + * + * @param d + * A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + + // Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key + * A key string. + * @return The object associated with the key. + * @throws JSONException + * if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with a key. + * + * @param key + * A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or + * "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = get(key); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a Boolean."); + } + + /** + * Get the double value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number."); + } + } + + /** + * Get the int value associated with a key. + * + * @param key + * A key string. + * @return The integer value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an integer. + */ + public int getInt(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not an int."); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key + * A key string. + * @return The long value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to a long. + */ + public long getLong(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a long."); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator iterator = jo.keys(); + String[] names = new String[length]; + int i = 0; + while (iterator.hasNext()) { + names[i] = (String) iterator.next(); + i += 1; + } + return names; + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key + * A key string. + * @return A string which is the value. + * @throws JSONException + * if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key + * A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if it is + * an Integer, Long, Double, or Float, then add one to it. + * + * @param key + * A key string. + * @return this. + * @throws JSONException + * If there is already a property with this name that is not an + * Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = opt(key); + if (value == null) { + put(key, 1); + } else if (value instanceof Integer) { + put(key, ((Integer) value).intValue() + 1); + } else if (value instanceof Long) { + put(key, ((Long) value).longValue() + 1); + } else if (value instanceof Double) { + put(key, ((Double) value).doubleValue() + 1); + } else if (value instanceof Float) { + put(key, ((Float) value).floatValue() + 1); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if there is no + * value. + * + * @param key + * A key string. + * @return true if there is no value associated with the key or if the value + * is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return map.keySet().iterator(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return map.size(); + } + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator keys = keys(); + while (keys.hasNext()) { + ja.put(keys.next()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Produce a string from a Number. + * + * @param number + * A Number + * @return A String. + * @throws JSONException + * If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key + * A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : map.get(key); + } + + /** + * Get an optional boolean associated with a key. It returns false if there + * is no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key + * A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the + * defaultValue if there is no such key, or if it is not a Boolean or the + * String "true" or "false" (case insensitive). + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key + * A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + return getDouble(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return getInt(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if + * there is no such key, or if its value is not a JSONObject. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return getLong(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional string associated with a key. It returns an empty string + * if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key + * A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + private void populateMap(Object bean) { + Class klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = (includeSuperClass) ? klass.getMethods() : klass + .getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if (name.equals("getClass") + || name.equals("getDeclaringClass")) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (key.length() > 0 + && Character.isUpperCase(key.charAt(0)) + && method.getParameterTypes().length == 0) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + + key.substring(1); + } + + Object result = method.invoke(bean, (Object[]) null); + if (result != null) { + map.put(key, wrap(result)); + } + } + } + } catch (Exception ignore) { + } + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A boolean which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * + * @param key + * A key string. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection value) throws JSONException { + put(key, new JSONArray(value)); + return this; + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A double which is the value. + * @return this. + * @throws JSONException + * If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) throws JSONException { + put(key, new Double(value)); + return this; + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * An int which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + put(key, new Integer(value)); + return this; + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A long which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + put(key, new Long(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key + * A key string. + * @param value + * A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) throws JSONException { + put(key, new JSONObject(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the + * key will be removed from the JSONObject if it is present. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is non-finite number or if the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + if (value != null) { + testValidity(value); + map.put(key, value); + } else { + remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key + * @param value + * @return his. + * @throws JSONException + * if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + put(key, value); + } + return this; + } + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within </, producing <\/, + * allowing JSON text to be delivered in HTML. In JSON text, a string cannot + * contain a control character or an unescaped quote or backslash. + * + * @param string + * A String + * @return A String correctly formatted for insertion in a JSON text. + */ + public static String quote(String string) { + if (string == null || string.length() == 0) { + return "\"\""; + } + + char b; + char c = 0; + String hhhh; + int i; + int len = string.length(); + StringBuffer sb = new StringBuffer(len + 4); + + sb.append('"'); + for (i = 0; i < len; i += 1) { + b = c; + c = string.charAt(i); + switch (c) { + case '\\': + case '"': + sb.append('\\'); + sb.append(c); + break; + case '/': + if (b == '<') { + sb.append('\\'); + } + sb.append(c); + break; + case '\b': + sb.append("\\b"); + break; + case '\t': + sb.append("\\t"); + break; + case '\n': + sb.append("\\n"); + break; + case '\f': + sb.append("\\f"); + break; + case '\r': + sb.append("\\r"); + break; + default: + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + hhhh = "000" + Integer.toHexString(c); + sb.append("\\u" + hhhh.substring(hhhh.length() - 4)); + } else { + sb.append(c); + } + } + } + sb.append('"'); + return sb.toString(); + } + + /** + * Remove a name and its value, if present. + * + * @param key + * The name to be removed. + * @return The value that was associated with the name, or null if there was + * no value. + */ + public Object remove(String key) { + return map.remove(key); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string + * A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + Double d; + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. We support the + * non-standard 0x- convention. If a number cannot be produced, then the + * value will just be a string. Note that the 0x-, plus, and implied + * string conventions are non-standard. A JSON parser may accept + * non-JSON forms as long as it accepts all correct JSON forms. + */ + + char b = string.charAt(0); + if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { + if (b == '0' && string.length() > 2 + && (string.charAt(1) == 'x' || string.charAt(1) == 'X')) { + try { + return new Integer( + Integer.parseInt(string.substring(2), 16)); + } catch (Exception ignore) { + } + } + try { + if (string.indexOf('.') > -1 || string.indexOf('e') > -1 + || string.indexOf('E') > -1) { + d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = new Long(string); + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } + } + } catch (Exception ignore) { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o + * The object to test. + * @throws JSONException + * If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names + * A JSONArray containing a list of key strings. This determines + * the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException + * If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is + * added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + */ + @Override + public String toString() { + try { + Iterator keys = keys(); + StringBuffer sb = new StringBuffer("{"); + + while (keys.hasNext()) { + if (sb.length() > 1) { + sb.append(','); + } + Object o = keys.next(); + sb.append(quote(o.toString())); + sb.append(':'); + sb.append(valueToString(map.get(o))); + } + sb.append('}'); + return sb.toString(); + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + * @throws JSONException + * If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + return toString(indentFactor, 0); + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indentation of the top level. + * @return a printable, displayable, transmittable representation of the + * object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + * @throws JSONException + * If the object contains an invalid number. + */ + String toString(int indentFactor, int indent) throws JSONException { + int i; + int length = length(); + if (length == 0) { + return "{}"; + } + Iterator keys = keys(); + int newindent = indent + indentFactor; + Object object; + StringBuffer sb = new StringBuffer("{"); + if (length == 1) { + object = keys.next(); + sb.append(quote(object.toString())); + sb.append(": "); + sb.append(valueToString(map.get(object), indentFactor, indent)); + } else { + while (keys.hasNext()) { + object = keys.next(); + if (sb.length() > 1) { + sb.append(",\n"); + } else { + sb.append('\n'); + } + for (i = 0; i < newindent; i += 1) { + sb.append(' '); + } + sb.append(quote(object.toString())); + sb.append(": "); + sb.append(valueToString(map.get(object), indentFactor, + newindent)); + } + if (sb.length() > 1) { + sb.append('\n'); + for (i = 0; i < indent; i += 1) { + sb.append(' '); + } + } + } + sb.append('}'); + return sb.toString(); + } + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce the + * JSON text. The method is required to produce a strictly conforming text. + * If the object does not contain a toJSONString method (which is the most + * common case), then a text will be produced by other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @param value + * The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + * @throws JSONException + * If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object instanceof String) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject + || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + return new JSONObject((Map) value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + /** + * Make a prettyprinted JSON text of an object value. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @param value + * The value to be serialized. + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indentation of the top level. + * @return a printable, displayable, transmittable representation of the + * object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + * @throws JSONException + * If the object contains an invalid number. + */ + static String valueToString(Object value, int indentFactor, int indent) + throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + try { + if (value instanceof JSONString) { + Object o = ((JSONString) value).toJSONString(); + if (o instanceof String) { + return (String) o; + } + } + } catch (Exception ignore) { + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean) { + return value.toString(); + } + if (value instanceof JSONObject) { + return ((JSONObject) value).toString(indentFactor, indent); + } + if (value instanceof JSONArray) { + return ((JSONArray) value).toString(indentFactor, indent); + } + if (value instanceof Map) { + return new JSONObject((Map) value).toString(indentFactor, indent); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).toString(indentFactor, + indent); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(indentFactor, indent); + } + return quote(value.toString()); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If it is + * a map, wrap it in a JSONObject. If it is a standard property (Double, + * String, et al) then it is already wrapped. Otherwise, if it comes from + * one of the java packages, turn it into a string. And if it doesn't, try + * to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object + * The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray + || NULL.equals(object) || object instanceof JSONString + || object instanceof Byte || object instanceof Character + || object instanceof Short || object instanceof Integer + || object instanceof Long || object instanceof Boolean + || object instanceof Float || object instanceof Double + || object instanceof String) { + return object; + } + + if (object instanceof Collection) { + return new JSONArray((Collection) object); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + return new JSONObject((Map) object); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage + .getName() : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + try { + boolean commanate = false; + Iterator keys = keys(); + writer.write('{'); + + while (keys.hasNext()) { + if (commanate) { + writer.write(','); + } + Object key = keys.next(); + writer.write(quote(key.toString())); + writer.write(':'); + Object value = map.get(key); + if (value instanceof JSONObject) { + ((JSONObject) value).write(writer); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer); + } else { + writer.write(valueToString(value)); + } + commanate = true; + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } +}
\ No newline at end of file diff --git a/src/com/vaadin/external/json/JSONString.java b/src/com/vaadin/external/json/JSONString.java new file mode 100644 index 0000000000..cc7e4d8c07 --- /dev/null +++ b/src/com/vaadin/external/json/JSONString.java @@ -0,0 +1,21 @@ +package com.vaadin.external.json; + +import java.io.Serializable; + +/** + * The <code>JSONString</code> interface allows a <code>toJSONString()</code> + * method so that a class can change the behavior of + * <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>, and + * <code>JSONWriter.value(</code>Object<code>)</code>. The + * <code>toJSONString</code> method will be used instead of the default behavior + * of using the Object's <code>toString()</code> method and quoting the result. + */ +public interface JSONString extends Serializable { + /** + * The <code>toJSONString</code> method allows a class to produce its own + * JSON serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} diff --git a/src/com/vaadin/external/json/JSONStringer.java b/src/com/vaadin/external/json/JSONStringer.java new file mode 100644 index 0000000000..e4ccc8e195 --- /dev/null +++ b/src/com/vaadin/external/json/JSONStringer.java @@ -0,0 +1,83 @@ +package com.vaadin.external.json; + +/* + Copyright (c) 2006 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.StringWriter; + +/** + * JSONStringer provides a quick and convenient way of producing JSON text. The + * texts produced strictly conform to JSON syntax rules. No whitespace is added, + * so the results are ready for transmission or storage. Each instance of + * JSONStringer can produce one JSON text. + * <p> + * A JSONStringer instance provides a <code>value</code> method for appending + * values to the text, and a <code>key</code> method for adding keys before + * values in objects. There are <code>array</code> and <code>endArray</code> + * methods that make and bound array values, and <code>object</code> and + * <code>endObject</code> methods which make and bound object values. All of + * these methods return the JSONWriter instance, permitting cascade style. For + * example, + * + * <pre> + * myString = new JSONStringer().object().key("JSON").value("Hello, World!") + * .endObject().toString(); + * </pre> + * + * which produces the string + * + * <pre> + * {"JSON":"Hello, World!"} + * </pre> + * <p> + * The first method called must be <code>array</code> or <code>object</code>. + * There are no methods for adding commas or colons. JSONStringer adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + * <p> + * This can sometimes be easier than using a JSONObject to build a string. + * + * @author JSON.org + * @version 2008-09-18 + */ +public class JSONStringer extends JSONWriter { + /** + * Make a fresh JSONStringer. It can be used to build one JSON text. + */ + public JSONStringer() { + super(new StringWriter()); + } + + /** + * Return the JSON text. This method is used to obtain the product of the + * JSONStringer instance. It will return <code>null</code> if there was a + * problem in the construction of the JSON text (such as the calls to + * <code>array</code> were not properly balanced with calls to + * <code>endArray</code>). + * + * @return The JSON text. + */ + public String toString() { + return this.mode == 'd' ? this.writer.toString() : null; + } +} diff --git a/src/com/vaadin/external/json/JSONTokener.java b/src/com/vaadin/external/json/JSONTokener.java new file mode 100644 index 0000000000..c3531cae1d --- /dev/null +++ b/src/com/vaadin/external/json/JSONTokener.java @@ -0,0 +1,451 @@ +package com.vaadin.external.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Serializable; +import java.io.StringReader; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +/** + * A JSONTokener takes a source string and extracts characters and tokens from + * it. It is used by the JSONObject and JSONArray constructors to parse JSON + * source strings. + * + * @author JSON.org + * @version 2010-12-24 + */ +public class JSONTokener implements Serializable { + + private int character; + private boolean eof; + private int index; + private int line; + private char previous; + private Reader reader; + private boolean usePrevious; + + /** + * Construct a JSONTokener from a Reader. + * + * @param reader + * A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() ? reader : new BufferedReader( + reader); + eof = false; + usePrevious = false; + previous = 0; + index = 0; + character = 1; + line = 1; + } + + /** + * Construct a JSONTokener from an InputStream. + */ + public JSONTokener(InputStream inputStream) throws JSONException { + this(new InputStreamReader(inputStream)); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s + * A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + /** + * Back up one character. This provides a sort of lookahead capability, so + * that you can test for a digit or letter before attempting to parse the + * next number or identifier. + */ + public void back() throws JSONException { + if (usePrevious || index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + index -= 1; + character -= 1; + usePrevious = true; + eof = false; + } + + /** + * Get the hex value of a character (base16). + * + * @param c + * A character between '0' and '9' or between 'A' and 'F' or + * between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + public boolean end() { + return eof && !usePrevious; + } + + /** + * Determine if the source string still contains characters that next() can + * consume. + * + * @return true if not yet at the end of the source. + */ + public boolean more() throws JSONException { + next(); + if (end()) { + return false; + } + back(); + return true; + } + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + */ + public char next() throws JSONException { + int c; + if (usePrevious) { + usePrevious = false; + c = previous; + } else { + try { + c = reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + + if (c <= 0) { // End of stream + eof = true; + c = 0; + } + } + index += 1; + if (previous == '\r') { + line += 1; + character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + line += 1; + character = 0; + } else { + character += 1; + } + previous = (char) c; + return previous; + } + + /** + * Consume the next character, and check that it matches a specified + * character. + * + * @param c + * The character to match. + * @return The character. + * @throws JSONException + * if the character does not match. + */ + public char next(char c) throws JSONException { + char n = next(); + if (n != c) { + throw syntaxError("Expected '" + c + "' and instead saw '" + n + + "'"); + } + return n; + } + + /** + * Get the next n characters. + * + * @param n + * The number of characters to take. + * @return A string of n characters. + * @throws JSONException + * Substring bounds error if there are not n characters + * remaining in the source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + + while (pos < n) { + chars[pos] = next(); + if (end()) { + throw syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + /** + * Get the next char in the string, skipping whitespace. + * + * @throws JSONException + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + /** + * Return the characters up to the next close quote character. Backslash + * processing is done. The formal JSON format does not allow strings in + * single quotes, but an implementation is allowed to accept them. + * + * @param quote + * The quoting character, either <code>"</code> + * <small>(double quote)</small> or <code>'</code> + * <small>(single quote)</small>. + * @return A String. + * @throws JSONException + * Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw syntaxError("Unterminated string"); + case '\\': + c = next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(next(4), 16)); + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + /** + * Get the text up but not including the specified character or the end of + * line, whichever comes first. + * + * @param delimiter + * A delimiter character. + * @return A string. + */ + public String nextTo(char delimiter) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (;;) { + char c = next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter + * characters or the end of line, whichever comes first. + * + * @param delimiters + * A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the next value. The value can be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * + * @throws JSONException + * If syntax error. + * + * @return An object. + */ + public Object nextValue() throws JSONException { + char c = nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return nextString(c); + case '{': + back(); + return new JSONObject(this); + case '[': + back(); + return new JSONArray(this); + } + + /* + * Handle unquoted text. This could be the values true, false, or null, + * or it can be a number. An implementation (such as this one) is + * allowed to also accept non-standard forms. + * + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + + StringBuffer sb = new StringBuffer(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = next(); + } + back(); + + string = sb.toString().trim(); + if (string.equals("")) { + throw syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. If + * the requested character is not found, no characters are skipped. + * + * @param to + * A character to skip to. + * @return The requested character, or zero if the requested character is + * not found. + */ + public char skipTo(char to) throws JSONException { + char c; + try { + int startIndex = index; + int startCharacter = character; + int startLine = line; + reader.mark(Integer.MAX_VALUE); + do { + c = next(); + if (c == 0) { + reader.reset(); + index = startIndex; + character = startCharacter; + line = startLine; + return c; + } + } while (c != to); + } catch (IOException exc) { + throw new JSONException(exc); + } + + back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message + * The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + toString()); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override + public String toString() { + return " at " + index + " [character " + character + " line " + line + + "]"; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/external/json/JSONWriter.java b/src/com/vaadin/external/json/JSONWriter.java new file mode 100644 index 0000000000..5f9ddeeae2 --- /dev/null +++ b/src/com/vaadin/external/json/JSONWriter.java @@ -0,0 +1,355 @@ +package com.vaadin.external.json; + +import java.io.IOException; +import java.io.Serializable; +import java.io.Writer; + +/* + Copyright (c) 2006 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +/** + * JSONWriter provides a quick and convenient way of producing JSON text. The + * texts produced strictly conform to JSON syntax rules. No whitespace is added, + * so the results are ready for transmission or storage. Each instance of + * JSONWriter can produce one JSON text. + * <p> + * A JSONWriter instance provides a <code>value</code> method for appending + * values to the text, and a <code>key</code> method for adding keys before + * values in objects. There are <code>array</code> and <code>endArray</code> + * methods that make and bound array values, and <code>object</code> and + * <code>endObject</code> methods which make and bound object values. All of + * these methods return the JSONWriter instance, permitting a cascade style. For + * example, + * + * <pre> + * new JSONWriter(myWriter).object().key("JSON").value("Hello, World!") + * .endObject(); + * </pre> + * + * which writes + * + * <pre> + * {"JSON":"Hello, World!"} + * </pre> + * <p> + * The first method called must be <code>array</code> or <code>object</code>. + * There are no methods for adding commas or colons. JSONWriter adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + * <p> + * This can sometimes be easier than using a JSONObject to build a string. + * + * @author JSON.org + * @version 2011-11-14 + */ +public class JSONWriter implements Serializable { + private static final int maxdepth = 200; + + /** + * The comma flag determines if a comma should be output before the next + * value. + */ + private boolean comma; + + /** + * The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k' + * (key), 'o' (object). + */ + protected char mode; + + /** + * The object/array stack. + */ + private final JSONObject stack[]; + + /** + * The stack top index. A value of 0 indicates that the stack is empty. + */ + private int top; + + /** + * The writer that will receive the output. + */ + protected Writer writer; + + /** + * Make a fresh JSONWriter. It can be used to build one JSON text. + */ + public JSONWriter(Writer w) { + comma = false; + mode = 'i'; + stack = new JSONObject[maxdepth]; + top = 0; + writer = w; + } + + /** + * Append a value. + * + * @param string + * A string value. + * @return this + * @throws JSONException + * If the value is out of sequence. + */ + private JSONWriter append(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null pointer"); + } + if (mode == 'o' || mode == 'a') { + try { + if (comma && mode == 'a') { + writer.write(','); + } + writer.write(string); + } catch (IOException e) { + throw new JSONException(e); + } + if (mode == 'o') { + mode = 'k'; + } + comma = true; + return this; + } + throw new JSONException("Value out of sequence."); + } + + /** + * Begin appending a new array. All values until the balancing + * <code>endArray</code> will be appended to this array. The + * <code>endArray</code> method must be called to mark the array's end. + * + * @return this + * @throws JSONException + * If the nesting is too deep, or if the object is started in + * the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter array() throws JSONException { + if (mode == 'i' || mode == 'o' || mode == 'a') { + push(null); + append("["); + comma = false; + return this; + } + throw new JSONException("Misplaced array."); + } + + /** + * End something. + * + * @param mode + * Mode + * @param c + * Closing character + * @return this + * @throws JSONException + * If unbalanced. + */ + private JSONWriter end(char mode, char c) throws JSONException { + if (this.mode != mode) { + throw new JSONException(mode == 'a' ? "Misplaced endArray." + : "Misplaced endObject."); + } + pop(mode); + try { + writer.write(c); + } catch (IOException e) { + throw new JSONException(e); + } + comma = true; + return this; + } + + /** + * End an array. This method most be called to balance calls to + * <code>array</code>. + * + * @return this + * @throws JSONException + * If incorrectly nested. + */ + public JSONWriter endArray() throws JSONException { + return end('a', ']'); + } + + /** + * End an object. This method most be called to balance calls to + * <code>object</code>. + * + * @return this + * @throws JSONException + * If incorrectly nested. + */ + public JSONWriter endObject() throws JSONException { + return end('k', '}'); + } + + /** + * Append a key. The key will be associated with the next value. In an + * object, every value must be preceded by a key. + * + * @param string + * A key string. + * @return this + * @throws JSONException + * If the key is out of place. For example, keys do not belong + * in arrays or if the key is null. + */ + public JSONWriter key(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null key."); + } + if (mode == 'k') { + try { + stack[top - 1].putOnce(string, Boolean.TRUE); + if (comma) { + writer.write(','); + } + writer.write(JSONObject.quote(string)); + writer.write(':'); + comma = false; + mode = 'o'; + return this; + } catch (IOException e) { + throw new JSONException(e); + } + } + throw new JSONException("Misplaced key."); + } + + /** + * Begin appending a new object. All keys and values until the balancing + * <code>endObject</code> will be appended to this object. The + * <code>endObject</code> method must be called to mark the object's end. + * + * @return this + * @throws JSONException + * If the nesting is too deep, or if the object is started in + * the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter object() throws JSONException { + if (mode == 'i') { + mode = 'o'; + } + if (mode == 'o' || mode == 'a') { + append("{"); + push(new JSONObject()); + comma = false; + return this; + } + throw new JSONException("Misplaced object."); + + } + + /** + * Pop an array or object scope. + * + * @param c + * The scope to close. + * @throws JSONException + * If nesting is wrong. + */ + private void pop(char c) throws JSONException { + if (top <= 0) { + throw new JSONException("Nesting error."); + } + char m = stack[top - 1] == null ? 'a' : 'k'; + if (m != c) { + throw new JSONException("Nesting error."); + } + top -= 1; + mode = top == 0 ? 'd' : stack[top - 1] == null ? 'a' : 'k'; + } + + /** + * Push an array or object scope. + * + * @param c + * The scope to open. + * @throws JSONException + * If nesting is too deep. + */ + private void push(JSONObject jo) throws JSONException { + if (top >= maxdepth) { + throw new JSONException("Nesting too deep."); + } + stack[top] = jo; + mode = jo == null ? 'a' : 'k'; + top += 1; + } + + /** + * Append either the value <code>true</code> or the value <code>false</code> + * . + * + * @param b + * A boolean. + * @return this + * @throws JSONException + */ + public JSONWriter value(boolean b) throws JSONException { + return append(b ? "true" : "false"); + } + + /** + * Append a double value. + * + * @param d + * A double. + * @return this + * @throws JSONException + * If the number is not finite. + */ + public JSONWriter value(double d) throws JSONException { + return this.value(new Double(d)); + } + + /** + * Append a long value. + * + * @param l + * A long. + * @return this + * @throws JSONException + */ + public JSONWriter value(long l) throws JSONException { + return append(Long.toString(l)); + } + + /** + * Append an object value. + * + * @param object + * The object to append. It can be null, or a Boolean, Number, + * String, JSONObject, or JSONArray, or an object that implements + * JSONString. + * @return this + * @throws JSONException + * If the value is out of sequence. + */ + public JSONWriter value(Object object) throws JSONException { + return append(JSONObject.valueToString(object)); + } +} diff --git a/src/com/vaadin/external/json/README b/src/com/vaadin/external/json/README new file mode 100644 index 0000000000..ca6dc11764 --- /dev/null +++ b/src/com/vaadin/external/json/README @@ -0,0 +1,68 @@ +JSON in Java [package org.json] + +Douglas Crockford +douglas@crockford.com + +2011-02-02 + + +JSON is a light-weight, language independent, data interchange format. +See http://www.JSON.org/ + +The files in this package implement JSON encoders/decoders in Java. +It also includes the capability to convert between JSON and XML, HTTP +headers, Cookies, and CDL. + +This is a reference implementation. There is a large number of JSON packages +in Java. Perhaps someday the Java community will standardize on one. Until +then, choose carefully. + +The license includes this restriction: "The software shall be used for good, +not evil." If your conscience cannot live with that, then choose a different +package. + +The package compiles on Java 1.2 thru Java 1.4. + + +JSONObject.java: The JSONObject can parse text from a String or a JSONTokener +to produce a map-like object. The object provides methods for manipulating its +contents, and for producing a JSON compliant object serialization. + +JSONArray.java: The JSONObject can parse text from a String or a JSONTokener +to produce a vector-like object. The object provides methods for manipulating +its contents, and for producing a JSON compliant array serialization. + +JSONTokener.java: The JSONTokener breaks a text into a sequence of individual +tokens. It can be constructed from a String, Reader, or InputStream. + +JSONException.java: The JSONException is the standard exception type thrown +by this package. + + +JSONString.java: The JSONString interface requires a toJSONString method, +allowing an object to provide its own serialization. + +JSONStringer.java: The JSONStringer provides a convenient facility for +building JSON strings. + +JSONWriter.java: The JSONWriter provides a convenient facility for building +JSON text through a writer. + + +CDL.java: CDL provides support for converting between JSON and comma +delimited lists. + +Cookie.java: Cookie provides support for converting between JSON and cookies. + +CookieList.java: CookieList provides support for converting between JSON and +cookie lists. + +HTTP.java: HTTP provides support for converting between JSON and HTTP headers. + +HTTPTokener.java: HTTPTokener extends JSONTokener for parsing HTTP headers. + +XML.java: XML provides support for converting between JSON and XML. + +JSONML.java: JSONML provides support for converting between JSONML and XML. + +XMLTokener.java: XMLTokener extends JSONTokener for parsing XML text.
\ No newline at end of file diff --git a/src/com/vaadin/terminal/AbstractErrorMessage.java b/src/com/vaadin/terminal/AbstractErrorMessage.java new file mode 100644 index 0000000000..1a625fc0e6 --- /dev/null +++ b/src/com/vaadin/terminal/AbstractErrorMessage.java @@ -0,0 +1,169 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.data.Buffered; +import com.vaadin.data.Validator; +import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; + +/** + * Base class for component error messages. + * + * This class is used on the server side to construct the error messages to send + * to the client. + * + * @since 7.0 + */ +public abstract class AbstractErrorMessage implements ErrorMessage { + + public enum ContentMode { + /** + * Content mode, where the error contains only plain text. + */ + TEXT, + /** + * Content mode, where the error contains preformatted text. + */ + PREFORMATTED, + /** + * Content mode, where the error contains XHTML. + */ + XHTML; + } + + /** + * Content mode. + */ + private ContentMode mode = ContentMode.TEXT; + + /** + * Message in content mode. + */ + private String message; + + /** + * Error level. + */ + private ErrorLevel level = ErrorLevel.ERROR; + + private List<ErrorMessage> causes = new ArrayList<ErrorMessage>(); + + protected AbstractErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + protected void setMessage(String message) { + this.message = message; + } + + /* Documented in interface */ + public ErrorLevel getErrorLevel() { + return level; + } + + protected void setErrorLevel(ErrorLevel level) { + this.level = level; + } + + protected ContentMode getMode() { + return mode; + } + + protected void setMode(ContentMode mode) { + this.mode = mode; + } + + protected List<ErrorMessage> getCauses() { + return causes; + } + + protected void addCause(ErrorMessage cause) { + causes.add(cause); + } + + public String getFormattedHtmlMessage() { + String result = null; + switch (getMode()) { + case TEXT: + result = AbstractApplicationServlet.safeEscapeForHtml(getMessage()); + break; + case PREFORMATTED: + result = "<pre>" + + AbstractApplicationServlet + .safeEscapeForHtml(getMessage()) + "</pre>"; + break; + case XHTML: + result = getMessage(); + break; + } + // if no message, combine the messages of all children + if (null == result && null != getCauses() && getCauses().size() > 0) { + StringBuilder sb = new StringBuilder(); + for (ErrorMessage cause : getCauses()) { + String childMessage = cause.getFormattedHtmlMessage(); + if (null != childMessage) { + sb.append("<div>"); + sb.append(childMessage); + sb.append("</div>\n"); + } + } + if (sb.length() > 0) { + result = sb.toString(); + } + } + // still no message? use an empty string for backwards compatibility + if (null == result) { + result = ""; + } + return result; + } + + // TODO replace this with a helper method elsewhere? + public static ErrorMessage getErrorMessageForException(Throwable t) { + if (null == t) { + return null; + } else if (t instanceof ErrorMessage) { + // legacy case for custom error messages + return (ErrorMessage) t; + } else if (t instanceof Validator.InvalidValueException) { + UserError error = new UserError( + ((Validator.InvalidValueException) t).getHtmlMessage(), + ContentMode.XHTML, ErrorLevel.ERROR); + for (Validator.InvalidValueException nestedException : ((Validator.InvalidValueException) t) + .getCauses()) { + error.addCause(getErrorMessageForException(nestedException)); + } + return error; + } else if (t instanceof Buffered.SourceException) { + // no message, only the causes to be painted + UserError error = new UserError(null); + // in practice, this was always ERROR in Vaadin 6 unless tweaked in + // custom exceptions implementing ErrorMessage + error.setErrorLevel(ErrorLevel.ERROR); + // causes + for (Throwable nestedException : ((Buffered.SourceException) t) + .getCauses()) { + error.addCause(getErrorMessageForException(nestedException)); + } + return error; + } else { + return new SystemError(t); + } + } + + /* Documented in superclass */ + @Override + public String toString() { + return getMessage(); + } + +} diff --git a/src/com/vaadin/terminal/CombinedRequest.java b/src/com/vaadin/terminal/CombinedRequest.java new file mode 100644 index 0000000000..ccef6d8963 --- /dev/null +++ b/src/com/vaadin/terminal/CombinedRequest.java @@ -0,0 +1,167 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import com.vaadin.Application; +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.gwt.server.WebApplicationContext; +import com.vaadin.terminal.gwt.server.WebBrowser; + +/** + * A {@link WrappedRequest} with path and parameters from one request and + * {@link WrappedRequest.BrowserDetails} extracted from another request. + * + * This class is intended to be used for a two request initialization where the + * first request fetches the actual application page and the second request + * contains information extracted from the browser using javascript. + * + */ +public class CombinedRequest implements WrappedRequest { + + private final WrappedRequest secondRequest; + private Map<String, String[]> parameterMap; + + /** + * Creates a new combined request based on the second request and some + * details from the first request. + * + * @param secondRequest + * the second request which will be used as the foundation of the + * combined request + * @throws JSONException + * if the initialParams parameter can not be decoded + */ + public CombinedRequest(WrappedRequest secondRequest) throws JSONException { + this.secondRequest = secondRequest; + + HashMap<String, String[]> map = new HashMap<String, String[]>(); + JSONObject initialParams = new JSONObject( + secondRequest.getParameter("initialParams")); + for (Iterator<?> keys = initialParams.keys(); keys.hasNext();) { + String name = (String) keys.next(); + JSONArray jsonValues = initialParams.getJSONArray(name); + String[] values = new String[jsonValues.length()]; + for (int i = 0; i < values.length; i++) { + values[i] = jsonValues.getString(i); + } + map.put(name, values); + } + + parameterMap = Collections.unmodifiableMap(map); + + } + + public String getParameter(String parameter) { + String[] strings = getParameterMap().get(parameter); + if (strings == null || strings.length == 0) { + return null; + } else { + return strings[0]; + } + } + + public Map<String, String[]> getParameterMap() { + return parameterMap; + } + + public int getContentLength() { + return secondRequest.getContentLength(); + } + + public InputStream getInputStream() throws IOException { + return secondRequest.getInputStream(); + } + + public Object getAttribute(String name) { + return secondRequest.getAttribute(name); + } + + public void setAttribute(String name, Object value) { + secondRequest.setAttribute(name, value); + } + + public String getRequestPathInfo() { + return secondRequest.getParameter("initialPath"); + } + + public int getSessionMaxInactiveInterval() { + return secondRequest.getSessionMaxInactiveInterval(); + } + + public Object getSessionAttribute(String name) { + return secondRequest.getSessionAttribute(name); + } + + public void setSessionAttribute(String name, Object attribute) { + secondRequest.setSessionAttribute(name, attribute); + } + + public String getContentType() { + return secondRequest.getContentType(); + } + + public BrowserDetails getBrowserDetails() { + return new BrowserDetails() { + public String getUriFragment() { + String fragment = secondRequest.getParameter("fr"); + if (fragment == null) { + return ""; + } else { + return fragment; + } + } + + public String getWindowName() { + return secondRequest.getParameter("wn"); + } + + public WebBrowser getWebBrowser() { + WebApplicationContext context = (WebApplicationContext) Application + .getCurrentApplication().getContext(); + return context.getBrowser(); + } + }; + } + + /** + * Gets the original second request. This can be used e.g. if a request + * parameter from the second request is required. + * + * @return the original second wrapped request + */ + public WrappedRequest getSecondRequest() { + return secondRequest; + } + + public Locale getLocale() { + return secondRequest.getLocale(); + } + + public String getRemoteAddr() { + return secondRequest.getRemoteAddr(); + } + + public boolean isSecure() { + return secondRequest.isSecure(); + } + + public String getHeader(String name) { + return secondRequest.getHeader(name); + } + + public DeploymentConfiguration getDeploymentConfiguration() { + return secondRequest.getDeploymentConfiguration(); + } +} diff --git a/src/com/vaadin/terminal/CompositeErrorMessage.java b/src/com/vaadin/terminal/CompositeErrorMessage.java index aae231739e..b82b622f54 100644 --- a/src/com/vaadin/terminal/CompositeErrorMessage.java +++ b/src/com/vaadin/terminal/CompositeErrorMessage.java @@ -4,11 +4,8 @@ package com.vaadin.terminal; -import java.io.Serializable; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.List; /** * Class for combining multiple error messages together. @@ -19,17 +16,7 @@ import java.util.List; * @since 3.0 */ @SuppressWarnings("serial") -public class CompositeErrorMessage implements ErrorMessage, Serializable { - - /** - * Array of all the errors. - */ - private final List<ErrorMessage> errors; - - /** - * Level of the error. - */ - private int level; +public class CompositeErrorMessage extends AbstractErrorMessage { /** * Constructor for CompositeErrorMessage. @@ -39,14 +26,14 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { * ignored, but at least one message is required. */ public CompositeErrorMessage(ErrorMessage[] errorMessages) { - errors = new ArrayList<ErrorMessage>(errorMessages.length); - level = Integer.MIN_VALUE; + super(null); + setErrorLevel(ErrorLevel.INFORMATION); for (int i = 0; i < errorMessages.length; i++) { addErrorMessage(errorMessages[i]); } - if (errors.size() == 0) { + if (getCauses().size() == 0) { throw new IllegalArgumentException( "Composite error message must have at least one error"); } @@ -62,30 +49,21 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { */ public CompositeErrorMessage( Collection<? extends ErrorMessage> errorMessages) { - errors = new ArrayList<ErrorMessage>(errorMessages.size()); - level = Integer.MIN_VALUE; + super(null); + setErrorLevel(ErrorLevel.INFORMATION); for (final Iterator<? extends ErrorMessage> i = errorMessages .iterator(); i.hasNext();) { addErrorMessage(i.next()); } - if (errors.size() == 0) { + if (getCauses().size() == 0) { throw new IllegalArgumentException( "Composite error message must have at least one error"); } } /** - * The error level is the largest error level in - * - * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() - */ - public final int getErrorLevel() { - return level; - } - - /** * Adds a error message into this composite message. Updates the level * field. * @@ -93,11 +71,10 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { * the error message to be added. Duplicate errors are ignored. */ private void addErrorMessage(ErrorMessage error) { - if (error != null && !errors.contains(error)) { - errors.add(error); - final int l = error.getErrorLevel(); - if (l > level) { - level = l; + if (error != null && !getCauses().contains(error)) { + addCause(error); + if (error.getErrorLevel().intValue() > getErrorLevel().intValue()) { + setErrorLevel(error.getErrorLevel()); } } } @@ -108,55 +85,7 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { * @return the error iterator. */ public Iterator<ErrorMessage> iterator() { - return errors.iterator(); - } - - /** - * @see com.vaadin.terminal.Paintable#paint(com.vaadin.terminal.PaintTarget) - */ - public void paint(PaintTarget target) throws PaintException { - - if (errors.size() == 1) { - (errors.iterator().next()).paint(target); - } else { - target.startTag("error"); - - if (level > 0 && level <= ErrorMessage.INFORMATION) { - target.addAttribute("level", "info"); - } else if (level <= ErrorMessage.WARNING) { - target.addAttribute("level", "warning"); - } else if (level <= ErrorMessage.ERROR) { - target.addAttribute("level", "error"); - } else if (level <= ErrorMessage.CRITICAL) { - target.addAttribute("level", "critical"); - } else { - target.addAttribute("level", "system"); - } - - // Paint all the exceptions - for (final Iterator<ErrorMessage> i = errors.iterator(); i - .hasNext();) { - i.next().paint(target); - } - - target.endTag("error"); - } - } - - /* Documented in super interface */ - public void addListener(RepaintRequestListener listener) { - } - - /* Documented in super interface */ - public void removeListener(RepaintRequestListener listener) { - } - - /* Documented in super interface */ - public void requestRepaint() { - } - - /* Documented in super interface */ - public void requestRepaintRequests() { + return getCauses().iterator(); } /** @@ -168,7 +97,8 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { public String toString() { String retval = "["; int pos = 0; - for (final Iterator<ErrorMessage> i = errors.iterator(); i.hasNext();) { + for (final Iterator<ErrorMessage> i = getCauses().iterator(); i + .hasNext();) { if (pos > 0) { retval += ","; } @@ -179,13 +109,4 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { return retval; } - - public String getDebugId() { - return null; - } - - public void setDebugId(String id) { - throw new UnsupportedOperationException( - "Setting testing id for this Paintable is not implemented"); - } } diff --git a/src/com/vaadin/terminal/DeploymentConfiguration.java b/src/com/vaadin/terminal/DeploymentConfiguration.java new file mode 100644 index 0000000000..02a3f0200f --- /dev/null +++ b/src/com/vaadin/terminal/DeploymentConfiguration.java @@ -0,0 +1,86 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.Serializable; + +/** + * Provide deployment specific settings that are required outside terminal + * specific code. + * + * @author Vaadin Ltd. + * + * @since 7.0 + */ +public interface DeploymentConfiguration extends Serializable { + + /** + * Gets the base URL of the location of Vaadin's static files. + * + * @param request + * the request for which the location should be determined + * + * @return a string with the base URL for static files + */ + public String getStaticFileLocation(WrappedRequest request); + + /** + * Gets the widgetset that is configured for this deployment, e.g. from a + * parameter in web.xml. + * + * @param request + * the request for which a widgetset is required + * @return the name of the widgetset + */ + public String getConfiguredWidgetset(WrappedRequest request); + + /** + * Gets the theme that is configured for this deployment, e.g. from a portal + * parameter or just some sensible default value. + * + * @param request + * the request for which a theme is required + * @return the name of the theme + */ + public String getConfiguredTheme(WrappedRequest request); + + /** + * Checks whether the Vaadin application will be rendered on its own in the + * browser or whether it will be included into some other context. A + * standalone application may do things that might interfere with other + * parts of a page, e.g. changing the page title and requesting focus upon + * loading. + * + * @param request + * the request for which the application is loaded + * @return a boolean indicating whether the application should be standalone + */ + public boolean isStandalone(WrappedRequest request); + + /** + * Gets a configured property. The properties are typically read from e.g. + * web.xml or from system properties of the JVM. + * + * @param propertyName + * The simple of the property, in some contexts, lookup might be + * performed using variations of the provided name. + * @param defaultValue + * the default value that should be used if no value has been + * defined + * @return the property value, or the passed default value if no property + * value is found + */ + public String getApplicationOrSystemProperty(String propertyName, + String defaultValue); + + /** + * Get the class loader to use for loading classes loaded by name, e.g. + * custom Root classes. <code>null</code> indicates that the default class + * loader should be used. + * + * @return the class loader to use, or <code>null</code> + */ + public ClassLoader getClassLoader(); +} diff --git a/src/com/vaadin/terminal/DownloadStream.java b/src/com/vaadin/terminal/DownloadStream.java index 2db2a1f20d..9853b0eee2 100644 --- a/src/com/vaadin/terminal/DownloadStream.java +++ b/src/com/vaadin/terminal/DownloadStream.java @@ -4,12 +4,18 @@ package com.vaadin.terminal; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import javax.servlet.http.HttpServletResponse; + +import com.vaadin.terminal.gwt.server.Constants; + /** * Downloadable stream. * @@ -198,9 +204,132 @@ public class DownloadStream implements Serializable { * * @param bufferSize * the size of the buffer in bytes. + * + * @since 7.0 */ public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } + /** + * Writes this download stream to a wrapped response. This takes care of + * setting response headers according to what is defined in this download + * stream ({@link #getContentType()}, {@link #getCacheTime()}, + * {@link #getFileName()}) and transferring the data from the stream ( + * {@link #getStream()}) to the response. Defined parameters ( + * {@link #getParameterNames()}) are also included as headers in the + * response. If there's is a parameter named <code>Location</code>, a + * redirect (302 Moved temporarily) is sent instead of the contents of this + * stream. + * + * @param response + * the wrapped response to write this download stream to + * @throws IOException + * passed through from the wrapped response + * + * @since 7.0 + */ + public void writeTo(WrappedResponse response) throws IOException { + if (getParameter("Location") != null) { + response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + response.setHeader("Location", getParameter("Location")); + return; + } + + // Download from given stream + final InputStream data = getStream(); + if (data != null) { + + OutputStream out = null; + try { + // Sets content type + response.setContentType(getContentType()); + + // Sets cache headers + response.setCacheTime(getCacheTime()); + + // Copy download stream parameters directly + // to HTTP headers. + final Iterator<String> i = getParameterNames(); + if (i != null) { + while (i.hasNext()) { + final String param = i.next(); + response.setHeader(param, getParameter(param)); + } + } + + // suggest local filename from DownloadStream if + // Content-Disposition + // not explicitly set + String contentDispositionValue = getParameter("Content-Disposition"); + if (contentDispositionValue == null) { + contentDispositionValue = "filename=\"" + getFileName() + + "\""; + response.setHeader("Content-Disposition", + contentDispositionValue); + } + + int bufferSize = getBufferSize(); + if (bufferSize <= 0 || bufferSize > Constants.MAX_BUFFER_SIZE) { + bufferSize = Constants.DEFAULT_BUFFER_SIZE; + } + final byte[] buffer = new byte[bufferSize]; + int bytesRead = 0; + + out = response.getOutputStream(); + + long totalWritten = 0; + while ((bytesRead = data.read(buffer)) > 0) { + out.write(buffer, 0, bytesRead); + + totalWritten += bytesRead; + if (totalWritten >= buffer.length) { + // Avoid chunked encoding for small resources + out.flush(); + } + } + } finally { + tryToCloseStream(out); + tryToCloseStream(data); + } + } + } + + /** + * Helper method that tries to close an output stream and ignores any + * exceptions. + * + * @param out + * the output stream to close, <code>null</code> is also + * supported + */ + static void tryToCloseStream(OutputStream out) { + try { + // try to close output stream (e.g. file handle) + if (out != null) { + out.close(); + } + } catch (IOException e1) { + // NOP + } + } + + /** + * Helper method that tries to close an input stream and ignores any + * exceptions. + * + * @param in + * the input stream to close, <code>null</code> is also supported + */ + static void tryToCloseStream(InputStream in) { + try { + // try to close output stream (e.g. file handle) + if (in != null) { + in.close(); + } + } catch (IOException e1) { + // NOP + } + } + } diff --git a/src/com/vaadin/terminal/ErrorMessage.java b/src/com/vaadin/terminal/ErrorMessage.java index b3be407743..60a0780a72 100644 --- a/src/com/vaadin/terminal/ErrorMessage.java +++ b/src/com/vaadin/terminal/ErrorMessage.java @@ -15,66 +15,112 @@ import java.io.Serializable; * @VERSION@ * @since 3.0 */ -public interface ErrorMessage extends Paintable, Serializable { +public interface ErrorMessage extends Serializable { + + public enum ErrorLevel { + /** + * Error code for informational messages. + */ + INFORMATION("info", 0), + /** + * Error code for warning messages. + */ + WARNING("warning", 1), + /** + * Error code for regular error messages. + */ + ERROR("error", 2), + /** + * Error code for critical error messages. + */ + CRITICAL("critical", 3), + /** + * Error code for system errors and bugs. + */ + SYSTEMERROR("system", 4); + + String text; + int errorLevel; + + private ErrorLevel(String text, int errorLevel) { + this.text = text; + this.errorLevel = errorLevel; + } + + /** + * Textual representation for server-client communication of level + * + * @return String for error severity + */ + public String getText() { + return text; + } + + /** + * Integer representation of error severity for comparison + * + * @return integer for error severity + */ + public int intValue() { + return errorLevel; + } + + @Override + public String toString() { + return text; + } + + } /** - * Error code for system errors and bugs. + * @deprecated from 7.0, use {@link ErrorLevel#SYSTEMERROR} instead   */ - public static final int SYSTEMERROR = 5000; + @Deprecated + public static final ErrorLevel SYSTEMERROR = ErrorLevel.SYSTEMERROR; /** - * Error code for critical error messages. + * @deprecated from 7.0, use {@link ErrorLevel#CRITICAL} instead   */ - public static final int CRITICAL = 4000; + @Deprecated + public static final ErrorLevel CRITICAL = ErrorLevel.CRITICAL; /** - * Error code for regular error messages. + * @deprecated from 7.0, use {@link ErrorLevel#ERROR} instead   */ - public static final int ERROR = 3000; + + @Deprecated + public static final ErrorLevel ERROR = ErrorLevel.ERROR; /** - * Error code for warning messages. + * @deprecated from 7.0, use {@link ErrorLevel#WARNING} instead   */ - public static final int WARNING = 2000; + @Deprecated + public static final ErrorLevel WARNING = ErrorLevel.WARNING; /** - * Error code for informational messages. + * @deprecated from 7.0, use {@link ErrorLevel#INFORMATION} instead   */ - public static final int INFORMATION = 1000; + @Deprecated + public static final ErrorLevel INFORMATION = ErrorLevel.INFORMATION; /** * Gets the errors level. * * @return the level of error as an integer. */ - public int getErrorLevel(); + public ErrorLevel getErrorLevel(); /** - * Error messages are inmodifiable and thus listeners are not needed. This - * method should be implemented as empty. + * Returns the HTML formatted message to show in as the error message on the + * client. * - * @param listener - * the listener to be added. - * @see com.vaadin.terminal.Paintable#addListener(Paintable.RepaintRequestListener) - */ - public void addListener(RepaintRequestListener listener); - - /** - * Error messages are inmodifiable and thus listeners are not needed. This - * method should be implemented as empty. + * This method should perform any necessary escaping to avoid XSS attacks. * - * @param listener - * the listener to be removed. - * @see com.vaadin.terminal.Paintable#removeListener(Paintable.RepaintRequestListener) - */ - public void removeListener(RepaintRequestListener listener); - - /** - * Error messages are inmodifiable and thus listeners are not needed. This - * method should be implemented as empty. + * TODO this API may still change to use a separate data transfer object * - * @see com.vaadin.terminal.Paintable#requestRepaint() + * @return HTML formatted string for the error message + * @since 7.0 */ - public void requestRepaint(); + public String getFormattedHtmlMessage(); } diff --git a/src/com/vaadin/terminal/KeyMapper.java b/src/com/vaadin/terminal/KeyMapper.java index d44cd0de3a..3f19692ef1 100644 --- a/src/com/vaadin/terminal/KeyMapper.java +++ b/src/com/vaadin/terminal/KeyMapper.java @@ -5,7 +5,7 @@ package com.vaadin.terminal; import java.io.Serializable; -import java.util.Hashtable; +import java.util.HashMap; /** * <code>KeyMapper</code> is the simple two-way map for generating textual keys @@ -16,14 +16,13 @@ import java.util.Hashtable; * @VERSION@ * @since 3.0 */ -@SuppressWarnings("serial") -public class KeyMapper implements Serializable { +public class KeyMapper<V> implements Serializable { private int lastKey = 0; - private final Hashtable<Object, String> objectKeyMap = new Hashtable<Object, String>(); + private final HashMap<V, String> objectKeyMap = new HashMap<V, String>(); - private final Hashtable<String, Object> keyObjectMap = new Hashtable<String, Object>(); + private final HashMap<String, V> keyObjectMap = new HashMap<String, V>(); /** * Gets key for an object. @@ -31,7 +30,7 @@ public class KeyMapper implements Serializable { * @param o * the object. */ - public String key(Object o) { + public String key(V o) { if (o == null) { return "null"; @@ -58,8 +57,7 @@ public class KeyMapper implements Serializable { * the name with the desired value. * @return the object with the key. */ - public Object get(String key) { - + public V get(String key) { return keyObjectMap.get(key); } @@ -69,7 +67,7 @@ public class KeyMapper implements Serializable { * @param removeobj * the object to be removed. */ - public void remove(Object removeobj) { + public void remove(V removeobj) { final String key = objectKeyMap.get(removeobj); if (key != null) { diff --git a/src/com/vaadin/terminal/LegacyPaint.java b/src/com/vaadin/terminal/LegacyPaint.java new file mode 100644 index 0000000000..ea93e3db7f --- /dev/null +++ b/src/com/vaadin/terminal/LegacyPaint.java @@ -0,0 +1,85 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal; + +import java.io.Serializable; + +import com.vaadin.terminal.PaintTarget.PaintStatus; +import com.vaadin.ui.Component; +import com.vaadin.ui.HasComponents; + +public class LegacyPaint implements Serializable { + /** + * + * <p> + * Paints the Paintable into a UIDL stream. This method creates the UIDL + * sequence describing it and outputs it to the given UIDL stream. + * </p> + * + * <p> + * It is called when the contents of the component should be painted in + * response to the component first being shown or having been altered so + * that its visual representation is changed. + * </p> + * + * <p> + * <b>Do not override this to paint your component.</b> Override + * {@link #paintContent(PaintTarget)} instead. + * </p> + * + * + * @param target + * the target UIDL stream where the component should paint itself + * to. + * @throws PaintException + * if the paint operation failed. + */ + public static void paint(Component component, PaintTarget target) + throws PaintException { + // Only paint content of visible components. + if (!isVisibleInContext(component)) { + return; + } + + final String tag = target.getTag(component); + final PaintStatus status = target.startPaintable(component, tag); + if (PaintStatus.CACHED == status) { + // nothing to do but flag as cached and close the paintable tag + target.addAttribute("cached", true); + } else { + // Paint the contents of the component + if (component instanceof Vaadin6Component) { + ((Vaadin6Component) component).paintContent(target); + } + + } + target.endPaintable(component); + + } + + /** + * Checks if the component is visible and its parent is visible, + * recursively. + * <p> + * This is only a helper until paint is moved away from this class. + * + * @return + */ + protected static boolean isVisibleInContext(Component c) { + HasComponents p = c.getParent(); + while (p != null) { + if (!p.isVisible()) { + return false; + } + p = p.getParent(); + } + if (c.getParent() != null && !c.getParent().isComponentVisible(c)) { + return false; + } + + // All parents visible, return this state + return c.isVisible(); + } + +} diff --git a/src/com/vaadin/terminal/PaintTarget.java b/src/com/vaadin/terminal/PaintTarget.java index 68f68e2b51..9cfa324133 100644 --- a/src/com/vaadin/terminal/PaintTarget.java +++ b/src/com/vaadin/terminal/PaintTarget.java @@ -9,6 +9,7 @@ import java.util.Map; import com.vaadin.terminal.StreamVariable.StreamingStartEvent; import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.server.ClientConnector; /** * This interface defines the methods for painting XML to the UIDL stream. @@ -37,6 +38,26 @@ public interface PaintTarget extends Serializable { throws PaintException; /** + * Result of starting to paint a Paintable ( + * {@link PaintTarget#startPaintable(ClientConnector, String)}). + * + * @since 7.0 + */ + public enum PaintStatus { + /** + * Painting started, addVariable() and addAttribute() etc. methods may + * be called. + */ + PAINTING, + /** + * A previously unpainted or painted {@link Paintable} has been queued + * be created/update later in a separate change in the same set of + * changes. + */ + CACHED + }; + + /** * Prints element start tag of a paintable section. Starts a paintable * section using the given tag. The PaintTarget may implement a caching * scheme, that checks the paintable has actually changed or can a cached @@ -46,43 +67,45 @@ public interface PaintTarget extends Serializable { * omit the content and close the tag, in which case cached content should * be used. * </p> + * <p> + * This method may also add only a reference to the paintable and queue the + * paintable to be painted separately. + * </p> + * <p> + * Each paintable being painted should be closed by a matching + * {@link #endPaintable(ClientConnector)} regardless of the + * {@link PaintStatus} returned. + * </p> * * @param paintable * the paintable to start. * @param tag * the name of the start tag. - * @return <code>true</code> if paintable found in cache, <code>false</code> - * otherwise. + * @return {@link PaintStatus} - ready to paint or already cached on the + * client (also used for sub paintables that are painted later + * separately) * @throws PaintException * if the paint operation failed. * @see #startTag(String) - * @since 3.1 + * @since 7.0 (previously using startTag(Paintable, String)) */ - public boolean startTag(Paintable paintable, String tag) + public PaintStatus startPaintable(ClientConnector paintable, String tag) throws PaintException; /** - * Paints a component reference as an attribute to current tag. This method - * is meant to enable component interactions on client side. With reference - * the client side component can communicate directly to other component. + * Prints paintable element end tag. * - * Note! This was experimental api and got replaced by - * {@link #addAttribute(String, Paintable)} and - * {@link #addVariable(VariableOwner, String, Paintable)}. + * Calls to {@link #startPaintable(ClientConnector, String)}should be + * matched by {@link #endPaintable(ClientConnector)}. If the parent tag is + * closed before every child tag is closed a PaintException is raised. * * @param paintable - * the Paintable to reference - * @param referenceName + * the paintable to close. * @throws PaintException - * - * @since 5.2 - * @deprecated use {@link #addAttribute(String, Paintable)} or - * {@link #addVariable(VariableOwner, String, Paintable)} - * instead + * if the paint operation failed. + * @since 7.0 (previously using engTag(String)) */ - @Deprecated - public void paintReference(Paintable paintable, String referenceName) - throws PaintException; + public void endPaintable(ClientConnector paintable) throws PaintException; /** * Prints element start tag. @@ -266,7 +289,7 @@ public interface PaintTarget extends Serializable { * the Paintable to be referenced on client side * @throws PaintException */ - public void addAttribute(String name, Paintable value) + public void addAttribute(String name, ClientConnector value) throws PaintException; /** @@ -398,8 +421,8 @@ public interface PaintTarget extends Serializable { * @throws PaintException * if the paint oparation fails */ - public void addVariable(VariableOwner owner, String name, Paintable value) - throws PaintException; + public void addVariable(VariableOwner owner, String name, + ClientConnector value) throws PaintException; /** * Adds a upload stream type variable. @@ -470,14 +493,15 @@ public interface PaintTarget extends Serializable { /** * @return the "tag" string used in communication to present given - * {@link Paintable} type. Terminal may define how to present - * paintable. + * {@link ClientConnector} type. Terminal may define how to present + * the connector. */ - public String getTag(Paintable paintable); + public String getTag(ClientConnector paintable); /** * @return true if a full repaint has been requested. E.g. refresh in a * browser window or such. */ public boolean isFullRepaint(); + } diff --git a/src/com/vaadin/terminal/Paintable.java b/src/com/vaadin/terminal/Paintable.java deleted file mode 100644 index d043cb2606..0000000000 --- a/src/com/vaadin/terminal/Paintable.java +++ /dev/null @@ -1,147 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal; - -import java.io.Serializable; -import java.util.EventObject; - -/** - * Interface implemented by all classes that can be painted. Classes - * implementing this interface know how to output themselves to a UIDL stream - * and that way describing to the terminal how it should be displayed in the UI. - * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 3.0 - */ -public interface Paintable extends java.util.EventListener, Serializable { - - /** - * <p> - * Paints the Paintable into a UIDL stream. This method creates the UIDL - * sequence describing it and outputs it to the given UIDL stream. - * </p> - * - * <p> - * It is called when the contents of the component should be painted in - * response to the component first being shown or having been altered so - * that its visual representation is changed. - * </p> - * - * @param target - * the target UIDL stream where the component should paint itself - * to. - * @throws PaintException - * if the paint operation failed. - */ - public void paint(PaintTarget target) throws PaintException; - - /** - * Requests that the paintable should be repainted as soon as possible. - */ - public void requestRepaint(); - - /** - * Adds an unique id for component that get's transferred to terminal for - * testing purposes. Keeping identifiers unique throughout the Application - * instance is on programmers responsibility. - * <p> - * Note, that with the current terminal implementation the identifier cannot - * be changed while the component is visible. This means that the identifier - * should be set before the component is painted for the first time and kept - * the same while visible in the client. - * - * @param id - * A short (< 20 chars) alphanumeric id - */ - public void setDebugId(String id); - - /** - * Get's currently set debug identifier - * - * @return current debug id, null if not set - */ - public String getDebugId(); - - /** - * Repaint request event is thrown when the paintable needs to be repainted. - * This is typically done when the <code>paint</code> method would return - * dissimilar UIDL from the previous call of the method. - */ - @SuppressWarnings("serial") - public class RepaintRequestEvent extends EventObject { - - /** - * Constructs a new event. - * - * @param source - * the paintable needing repaint. - */ - public RepaintRequestEvent(Paintable source) { - super(source); - } - - /** - * Gets the paintable needing repainting. - * - * @return Paintable for which the <code>paint</code> method will return - * dissimilar UIDL from the previous call of the method. - */ - public Paintable getPaintable() { - return (Paintable) getSource(); - } - } - - /** - * Listens repaint requests. The <code>repaintRequested</code> method is - * called when the paintable needs to be repainted. This is typically done - * when the <code>paint</code> method would return dissimilar UIDL from the - * previous call of the method. - */ - public interface RepaintRequestListener extends Serializable { - - /** - * Receives repaint request events. - * - * @param event - * the repaint request event specifying the paintable source. - */ - public void repaintRequested(RepaintRequestEvent event); - } - - /** - * Adds repaint request listener. In order to assure that no repaint - * requests are missed, the new repaint listener should paint the paintable - * right after adding itself as listener. - * - * @param listener - * the listener to be added. - */ - public void addListener(RepaintRequestListener listener); - - /** - * Removes repaint request listener. - * - * @param listener - * the listener to be removed. - */ - public void removeListener(RepaintRequestListener listener); - - /** - * Request sending of repaint events on any further visible changes. - * Normally the paintable only send up to one repaint request for listeners - * after paint as the paintable as the paintable assumes that the listeners - * already know about the repaint need. This method resets the assumtion. - * Paint implicitly does the assumtion reset functionality implemented by - * this method. - * <p> - * This method is normally used only by the terminals to note paintables - * about implicit repaints (painting the component without actually invoking - * paint method). - * </p> - */ - public void requestRepaintRequests(); -} diff --git a/src/com/vaadin/terminal/ParameterHandler.java b/src/com/vaadin/terminal/ParameterHandler.java deleted file mode 100644 index ef8a952e0e..0000000000 --- a/src/com/vaadin/terminal/ParameterHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal; - -import java.io.Serializable; -import java.util.Map; - -import com.vaadin.ui.Window; - -/** - * {@code ParameterHandler} is implemented by classes capable of handling - * external parameters. - * - * <p> - * What parameters are provided depend on what the {@link Terminal} provides and - * if the application is deployed as a servlet or portlet. URL GET parameters - * are typically provided to the {@link #handleParameters(Map)} method. - * </p> - * <p> - * A {@code ParameterHandler} must be registered to a {@code Window} using - * {@link Window#addParameterHandler(ParameterHandler)} to be called when - * parameters are available. - * </p> - * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 3.0 - */ -public interface ParameterHandler extends Serializable { - - /** - * Handles the given parameters. All parameters names are of type - * {@link String} and the values are {@link String} arrays. - * - * @param parameters - * an unmodifiable map which contains the parameter names and - * values - * - */ - public void handleParameters(Map<String, String[]> parameters); - - /** - * An ErrorEvent implementation for ParameterHandler. - */ - public interface ErrorEvent extends Terminal.ErrorEvent { - - /** - * Gets the ParameterHandler that caused the error. - * - * @return the ParameterHandler that caused the error - */ - public ParameterHandler getParameterHandler(); - - } - -} diff --git a/src/com/vaadin/terminal/RequestHandler.java b/src/com/vaadin/terminal/RequestHandler.java new file mode 100644 index 0000000000..f37201715d --- /dev/null +++ b/src/com/vaadin/terminal/RequestHandler.java @@ -0,0 +1,36 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.IOException; +import java.io.Serializable; + +import com.vaadin.Application; + +/** + * Handler for producing a response to non-UIDL requests. Handlers can be added + * to applications using {@link Application#addRequestHandler(RequestHandler)} + */ +public interface RequestHandler extends Serializable { + + /** + * Handles a non-UIDL request. If a response is written, this method should + * return <code>false</code> to indicate that no more request handlers + * should be invoked for the request. + * + * @param application + * The application to which the request belongs + * @param request + * The request to handle + * @param response + * The response object to which a response can be written. + * @return true if a response has been written and no further request + * handlers should be called, otherwise false + * @throws IOException + */ + boolean handleRequest(Application application, WrappedRequest request, + WrappedResponse response) throws IOException; + +} diff --git a/src/com/vaadin/terminal/Scrollable.java b/src/com/vaadin/terminal/Scrollable.java index dac35c7704..472954c556 100644 --- a/src/com/vaadin/terminal/Scrollable.java +++ b/src/com/vaadin/terminal/Scrollable.java @@ -9,8 +9,7 @@ import java.io.Serializable; /** * <p> * This interface is implemented by all visual objects that can be scrolled - * programmatically from the server-side, or for which it is possible to know - * the scroll position on the server-side. The unit of scrolling is pixel. + * programmatically from the server-side. The unit of scrolling is pixel. * </p> * * @author Vaadin Ltd. @@ -40,17 +39,10 @@ public interface Scrollable extends Serializable { * scrolled right. * </p> * - * <p> - * The method only has effect if programmatic scrolling is enabled for the - * scrollable. Some implementations may require enabling programmatic before - * this method can be used. See {@link #setScrollable(boolean)} for more - * information. - * </p> - * - * @param pixelsScrolled + * @param scrollLeft * the xOffset. */ - public void setScrollLeft(int pixelsScrolled); + public void setScrollLeft(int scrollLeft); /** * Gets scroll top offset. @@ -73,13 +65,6 @@ public interface Scrollable extends Serializable { * </p> * * <p> - * The method only has effect if programmatic scrolling is enabled for the - * scrollable. Some implementations may require enabling programmatic before - * this method can be used. See {@link #setScrollable(boolean)} for more - * information. - * </p> - * - * <p> * The scrolling position is limited by the current height of the content * area. If the position is below the height, it is scrolled to the bottom. * However, if the same response also adds height to the content area, @@ -87,44 +72,9 @@ public interface Scrollable extends Serializable { * area. * </p> * - * @param pixelsScrolled + * @param scrollTop * the yOffset. */ - public void setScrollTop(int pixelsScrolled); - - /** - * Is programmatic scrolling enabled. - * - * <p> - * Whether programmatic scrolling with {@link #setScrollLeft(int)} and - * {@link #setScrollTop(int)} is enabled. - * </p> - * - * @return <code>true</code> if the scrolling is enabled, otherwise - * <code>false</code>. - */ - public boolean isScrollable(); - - /** - * Enables or disables programmatic scrolling. - * - * <p> - * Enables setting the scroll position with {@link #setScrollLeft(int)} and - * {@link #setScrollTop(int)}. Implementations of the interface may have - * programmatic scrolling disabled by default, in which case you need to - * enable it to use the mentioned methods. - * </p> - * - * <p> - * Notice that this does <i>not</i> control whether scroll bars are shown - * for a scrollable component. That normally happens automatically when the - * content grows too big for the component, relying on the "overflow: auto" - * property in CSS. - * </p> - * - * @param isScrollingEnabled - * true if the scrolling is allowed. - */ - public void setScrollable(boolean isScrollingEnabled); + public void setScrollTop(int scrollTop); } diff --git a/src/com/vaadin/terminal/Sizeable.java b/src/com/vaadin/terminal/Sizeable.java index f5dc28b74c..e3c98e0fa9 100644 --- a/src/com/vaadin/terminal/Sizeable.java +++ b/src/com/vaadin/terminal/Sizeable.java @@ -18,71 +18,127 @@ import java.io.Serializable; public interface Sizeable extends Serializable { /** - * Unit code representing pixels. + * @deprecated from 7.0, use {@link Unit#PIXELS} instead   */ - public static final int UNITS_PIXELS = 0; + @Deprecated + public static final Unit UNITS_PIXELS = Unit.PIXELS; /** - * Unit code representing points (1/72nd of an inch). + * @deprecated from 7.0, use {@link Unit#POINTS} instead   */ - public static final int UNITS_POINTS = 1; + @Deprecated + public static final Unit UNITS_POINTS = Unit.POINTS; /** - * Unit code representing picas (12 points). + * @deprecated from 7.0, use {@link Unit#PICAS} instead   */ - public static final int UNITS_PICAS = 2; + @Deprecated + public static final Unit UNITS_PICAS = Unit.PICAS; /** - * Unit code representing the font-size of the relevant font. + * @deprecated from 7.0, use {@link Unit#EM} instead   */ - public static final int UNITS_EM = 3; + @Deprecated + public static final Unit UNITS_EM = Unit.EM; /** - * Unit code representing the x-height of the relevant font. + * @deprecated from 7.0, use {@link Unit#EX} instead   */ - public static final int UNITS_EX = 4; + @Deprecated + public static final Unit UNITS_EX = Unit.EX; /** - * Unit code representing millimeters. + * @deprecated from 7.0, use {@link Unit#MM} instead   */ - public static final int UNITS_MM = 5; + @Deprecated + public static final Unit UNITS_MM = Unit.MM; /** - * Unit code representing centimeters. + * @deprecated from 7.0, use {@link Unit#CM} instead   */ - public static final int UNITS_CM = 6; + @Deprecated + public static final Unit UNITS_CM = Unit.CM; /** - * Unit code representing inches. + * @deprecated from 7.0, use {@link Unit#INCH} instead   */ - public static final int UNITS_INCH = 7; + @Deprecated + public static final Unit UNITS_INCH = Unit.INCH; /** - * Unit code representing in percentage of the containing element defined by - * terminal. + * @deprecated from 7.0, use {@link Unit#PERCENTAGE} instead   */ - public static final int UNITS_PERCENTAGE = 8; + @Deprecated + public static final Unit UNITS_PERCENTAGE = Unit.PERCENTAGE; public static final float SIZE_UNDEFINED = -1; - /** - * Textual representations of units symbols. Supported units and their - * symbols are: - * <ul> - * <li>{@link #UNITS_PIXELS}: "px"</li> - * <li>{@link #UNITS_POINTS}: "pt"</li> - * <li>{@link #UNITS_PICAS}: "pc"</li> - * <li>{@link #UNITS_EM}: "em"</li> - * <li>{@link #UNITS_EX}: "ex"</li> - * <li>{@link #UNITS_MM}: "mm"</li> - * <li>{@link #UNITS_CM}. "cm"</li> - * <li>{@link #UNITS_INCH}: "in"</li> - * <li>{@link #UNITS_PERCENTAGE}: "%"</li> - * </ul> - * These can be used like <code>Sizeable.UNIT_SYMBOLS[UNITS_PIXELS]</code>. - */ - public static final String[] UNIT_SYMBOLS = { "px", "pt", "pc", "em", "ex", - "mm", "cm", "in", "%" }; + public enum Unit { + /** + * Unit code representing pixels. + */ + PIXELS("px"), + /** + * Unit code representing points (1/72nd of an inch). + */ + POINTS("pt"), + /** + * Unit code representing picas (12 points). + */ + PICAS("pc"), + /** + * Unit code representing the font-size of the relevant font. + */ + EM("em"), + /** + * Unit code representing the x-height of the relevant font. + */ + EX("ex"), + /** + * Unit code representing millimeters. + */ + MM("mm"), + /** + * Unit code representing centimeters. + */ + CM("cm"), + /** + * Unit code representing inches. + */ + INCH("in"), + /** + * Unit code representing in percentage of the containing element + * defined by terminal. + */ + PERCENTAGE("%"); + + private String symbol; + + private Unit(String symbol) { + this.symbol = symbol; + } + + public String getSymbol() { + return symbol; + } + + @Override + public String toString() { + return symbol; + } + + public static Unit getUnitFromSymbol(String symbol) { + if (symbol == null) { + return Unit.PIXELS; // Defaults to pixels + } + for (Unit unit : Unit.values()) { + if (symbol.equals(unit.getSymbol())) { + return unit; + } + } + return Unit.PIXELS; // Defaults to pixels + } + } /** * Gets the width of the object. Negative number implies unspecified size @@ -93,21 +149,6 @@ public interface Sizeable extends Serializable { public float getWidth(); /** - * Sets the width of the object. Negative number implies unspecified size - * (terminal is free to set the size). - * - * @param width - * the width of the object in units specified by widthUnits - * property. - * @deprecated Consider using {@link #setWidth(String)} instead. This method - * works, but is error-prone since the unit must be set - * separately (and components might have different default - * unit). - */ - @Deprecated - public void setWidth(float width); - - /** * Gets the height of the object. Negative number implies unspecified size * (terminal is free to set the size). * @@ -116,57 +157,18 @@ public interface Sizeable extends Serializable { public float getHeight(); /** - * Sets the height of the object. Negative number implies unspecified size - * (terminal is free to set the size). - * - * @param height - * the height of the object in units specified by heightUnits - * property. - * @deprecated Consider using {@link #setHeight(String)} or - * {@link #setHeight(float, int)} instead. This method works, - * but is error-prone since the unit must be set separately (and - * components might have different default unit). - */ - @Deprecated - public void setHeight(float height); - - /** * Gets the width property units. * * @return units used in width property. */ - public int getWidthUnits(); - - /** - * Sets the width property units. - * - * @param units - * the units used in width property. - * @deprecated Consider setting width and unit simultaneously using - * {@link #setWidth(String)} or {@link #setWidth(float, int)}, - * which is less error-prone. - */ - @Deprecated - public void setWidthUnits(int units); + public Unit getWidthUnits(); /** * Gets the height property units. * * @return units used in height property. */ - public int getHeightUnits(); - - /** - * Sets the height property units. - * - * @param units - * the units used in height property. - * @deprecated Consider setting height and unit simultaneously using - * {@link #setHeight(String)} or {@link #setHeight(float, int)}, - * which is less error-prone. - */ - @Deprecated - public void setHeightUnits(int units); + public Unit getHeightUnits(); /** * Sets the height of the component using String presentation. @@ -193,13 +195,9 @@ public interface Sizeable extends Serializable { * @param width * the width of the object. * @param unit - * the unit used for the width. Possible values include - * {@link #UNITS_PIXELS}, {@link #UNITS_POINTS}, - * {@link #UNITS_PICAS}, {@link #UNITS_EM}, {@link #UNITS_EX}, - * {@link #UNITS_MM}, {@link #UNITS_CM}, {@link #UNITS_INCH}, - * {@link #UNITS_PERCENTAGE}. + * the unit used for the width. */ - public void setWidth(float width, int unit); + public void setWidth(float width, Unit unit); /** * Sets the height of the object. Negative number implies unspecified size @@ -208,13 +206,9 @@ public interface Sizeable extends Serializable { * @param height * the height of the object. * @param unit - * the unit used for the width. Possible values include - * {@link #UNITS_PIXELS}, {@link #UNITS_POINTS}, - * {@link #UNITS_PICAS}, {@link #UNITS_EM}, {@link #UNITS_EX}, - * {@link #UNITS_MM}, {@link #UNITS_CM}, {@link #UNITS_INCH}, - * {@link #UNITS_PERCENTAGE}. + * the unit used for the width. */ - public void setHeight(float height, int unit); + public void setHeight(float height, Unit unit); /** * Sets the width of the component using String presentation. diff --git a/src/com/vaadin/terminal/SystemError.java b/src/com/vaadin/terminal/SystemError.java index ce1483dbb5..bae135ee6b 100644 --- a/src/com/vaadin/terminal/SystemError.java +++ b/src/com/vaadin/terminal/SystemError.java @@ -4,16 +4,12 @@ package com.vaadin.terminal; -import java.io.PrintWriter; -import java.io.StringWriter; - import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; /** - * <code>SystemError</code> is a runtime exception caused by error in system. - * The system error can be shown to the user as it implements - * <code>ErrorMessage</code> interface, but contains technical information such - * as stack trace and exception. + * <code>SystemError</code> is an error message for a problem caused by error in + * system, not the user application code. The system error can contain technical + * information such as stack trace and exception. * * SystemError does not support HTML in error messages or stack traces. If HTML * messages are required, use {@link UserError} or a custom implementation of @@ -25,13 +21,7 @@ import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; * @since 3.0 */ @SuppressWarnings("serial") -public class SystemError extends RuntimeException implements ErrorMessage { - - /** - * The cause of the system error. The cause is stored separately as JDK 1.3 - * does not support causes natively. - */ - private Throwable cause = null; +public class SystemError extends AbstractErrorMessage { /** * Constructor for SystemError with error message specified. @@ -41,6 +31,9 @@ public class SystemError extends RuntimeException implements ErrorMessage { */ public SystemError(String message) { super(message); + setErrorLevel(ErrorLevel.SYSTEMERROR); + setMode(ContentMode.XHTML); + setMessage(getHtmlMessage()); } /** @@ -52,8 +45,8 @@ public class SystemError extends RuntimeException implements ErrorMessage { * the throwable causing the system error. */ public SystemError(String message, Throwable cause) { - super(message); - this.cause = cause; + this(message); + addCause(AbstractErrorMessage.getErrorMessageForException(cause)); } /** @@ -63,31 +56,7 @@ public class SystemError extends RuntimeException implements ErrorMessage { * the throwable causing the system error. */ public SystemError(Throwable cause) { - this.cause = cause; - } - - /** - * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() - */ - public final int getErrorLevel() { - return ErrorMessage.SYSTEMERROR; - } - - /** - * @see com.vaadin.terminal.Paintable#paint(com.vaadin.terminal.PaintTarget) - */ - public void paint(PaintTarget target) throws PaintException { - - target.startTag("error"); - target.addAttribute("level", "system"); - - String message = getHtmlMessage(); - - target.addXMLSection("div", message, - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); - - target.endTag("error"); - + this(null, cause); } /** @@ -96,61 +65,18 @@ public class SystemError extends RuntimeException implements ErrorMessage { * Note that this API may change in future versions. */ protected String getHtmlMessage() { + // TODO wrapping div with namespace? See the old code: + // target.addXMLSection("div", message, + // "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); + StringBuilder sb = new StringBuilder(); - final String message = getLocalizedMessage(); - if (message != null) { + if (getMessage() != null) { sb.append("<h2>"); - sb.append(AbstractApplicationServlet.safeEscapeForHtml(message)); + sb.append(AbstractApplicationServlet + .safeEscapeForHtml(getMessage())); sb.append("</h2>"); } - - // Paint the exception - if (cause != null) { - sb.append("<h3>Exception</h3>"); - final StringWriter buffer = new StringWriter(); - cause.printStackTrace(new PrintWriter(buffer)); - sb.append("<pre>"); - sb.append(AbstractApplicationServlet.safeEscapeForHtml(buffer - .toString())); - sb.append("</pre>"); - } return sb.toString(); } - /** - * Gets cause for the error. - * - * @return the cause. - * @see java.lang.Throwable#getCause() - */ - @Override - public Throwable getCause() { - return cause; - } - - /* Documented in super interface */ - public void addListener(RepaintRequestListener listener) { - } - - /* Documented in super interface */ - public void removeListener(RepaintRequestListener listener) { - } - - /* Documented in super interface */ - public void requestRepaint() { - } - - /* Documented in super interface */ - public void requestRepaintRequests() { - } - - public String getDebugId() { - return null; - } - - public void setDebugId(String id) { - throw new UnsupportedOperationException( - "Setting testing id for this Paintable is not implemented"); - } - } diff --git a/src/com/vaadin/terminal/URIHandler.java b/src/com/vaadin/terminal/URIHandler.java deleted file mode 100644 index b3fea0e3bf..0000000000 --- a/src/com/vaadin/terminal/URIHandler.java +++ /dev/null @@ -1,48 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal; - -import java.io.Serializable; -import java.net.URL; - -/** - * A URIHandler is used for handling URI:s requested by the user and can - * optionally provide a {@link DownloadStream}. If a {@link DownloadStream} is - * returned by {@link #handleURI(URL, String)}, the stream is sent to the - * client. - * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 3.0 - */ -public interface URIHandler extends Serializable { - - /** - * Handles a given URI. If the URI handler to emit a downloadable stream it - * should return a {@code DownloadStream} object. - * - * @param context - * the base URL - * @param relativeUri - * a URI relative to {@code context} - * @return A downloadable stream or null if no stream is provided - */ - public DownloadStream handleURI(URL context, String relativeUri); - - /** - * An {@code ErrorEvent} implementation for URIHandler. - */ - public interface ErrorEvent extends Terminal.ErrorEvent { - - /** - * Gets the URIHandler that caused this error. - * - * @return the URIHandler that caused the error - */ - public URIHandler getURIHandler(); - - } -} diff --git a/src/com/vaadin/terminal/UserError.java b/src/com/vaadin/terminal/UserError.java index 170d76610b..baaf331fa0 100644 --- a/src/com/vaadin/terminal/UserError.java +++ b/src/com/vaadin/terminal/UserError.java @@ -4,8 +4,6 @@ package com.vaadin.terminal; -import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; - /** * <code>UserError</code> is a controlled error occurred in application. User * errors are occur in normal usage of the application and guide the user. @@ -16,43 +14,25 @@ import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; * @since 3.0 */ @SuppressWarnings("serial") -public class UserError implements ErrorMessage { - - /** - * Content mode, where the error contains only plain text. - */ - public static final int CONTENT_TEXT = 0; +public class UserError extends AbstractErrorMessage { /** - * Content mode, where the error contains preformatted text. + * @deprecated from 7.0, use {@link ContentMode#TEXT} instead   */ - public static final int CONTENT_PREFORMATTED = 1; + @Deprecated + public static final ContentMode CONTENT_TEXT = ContentMode.TEXT; /** - * Formatted content mode, where the contents is XML restricted to the UIDL - * 1.0 formatting markups. + * @deprecated from 7.0, use {@link ContentMode#PREFORMATTED} instead   */ - public static final int CONTENT_UIDL = 2; + @Deprecated + public static final ContentMode CONTENT_PREFORMATTED = ContentMode.PREFORMATTED; /** - * Content mode, where the error contains XHTML. + * @deprecated from 7.0, use {@link ContentMode#XHTML} instead   */ - public static final int CONTENT_XHTML = 3; - - /** - * Content mode. - */ - private int mode = CONTENT_TEXT; - - /** - * Message in content mode. - */ - private final String msg; - - /** - * Error level. - */ - private int level = ErrorMessage.ERROR; + @Deprecated + public static final ContentMode CONTENT_XHTML = ContentMode.XHTML; /** * Creates a textual error message of level ERROR. @@ -61,105 +41,20 @@ public class UserError implements ErrorMessage { * the text of the error message. */ public UserError(String textErrorMessage) { - msg = textErrorMessage; + super(textErrorMessage); } - /** - * Creates a error message with level and content mode. - * - * @param message - * the error message. - * @param contentMode - * the content Mode. - * @param errorLevel - * the level of error. - */ - public UserError(String message, int contentMode, int errorLevel) { - - // Check the parameters - if (contentMode < 0 || contentMode > 2) { - throw new java.lang.IllegalArgumentException( - "Unsupported content mode: " + contentMode); + public UserError(String message, ContentMode contentMode, + ErrorLevel errorLevel) { + super(message); + if (contentMode == null) { + contentMode = ContentMode.TEXT; } - - msg = message; - mode = contentMode; - level = errorLevel; - } - - /* Documented in interface */ - public int getErrorLevel() { - return level; - } - - /* Documented in interface */ - public void addListener(RepaintRequestListener listener) { - } - - /* Documented in interface */ - public void removeListener(RepaintRequestListener listener) { - } - - /* Documented in interface */ - public void requestRepaint() { - } - - /* Documented in interface */ - public void paint(PaintTarget target) throws PaintException { - - target.startTag("error"); - - // Error level - if (level >= ErrorMessage.SYSTEMERROR) { - target.addAttribute("level", "system"); - } else if (level >= ErrorMessage.CRITICAL) { - target.addAttribute("level", "critical"); - } else if (level >= ErrorMessage.ERROR) { - target.addAttribute("level", "error"); - } else if (level >= ErrorMessage.WARNING) { - target.addAttribute("level", "warning"); - } else { - target.addAttribute("level", "info"); - } - - // Paint the message - switch (mode) { - case CONTENT_TEXT: - target.addText(AbstractApplicationServlet.safeEscapeForHtml(msg)); - break; - case CONTENT_UIDL: - target.addUIDL(msg); - break; - case CONTENT_PREFORMATTED: - target.addText("<pre>" - + AbstractApplicationServlet.safeEscapeForHtml(msg) - + "</pre>"); - break; - case CONTENT_XHTML: - target.addText(msg); - break; + if (errorLevel == null) { + errorLevel = ErrorLevel.ERROR; } - - target.endTag("error"); - } - - /* Documented in interface */ - public void requestRepaintRequests() { - } - - /* Documented in superclass */ - @Override - public String toString() { - return msg; - } - - public String getDebugId() { - return null; - } - - public void setDebugId(String id) { - throw new UnsupportedOperationException( - "Setting testing id for this Paintable is not implemented"); + setMode(contentMode); + setErrorLevel(errorLevel); } } diff --git a/src/com/vaadin/terminal/Vaadin6Component.java b/src/com/vaadin/terminal/Vaadin6Component.java new file mode 100644 index 0000000000..59cbf956ca --- /dev/null +++ b/src/com/vaadin/terminal/Vaadin6Component.java @@ -0,0 +1,44 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal; + +import java.util.EventListener; + +import com.vaadin.ui.Component; + +/** + * Interface provided to ease porting of Vaadin 6 components to Vaadin 7. By + * implementing this interface your Component will be able to use + * {@link #paintContent(PaintTarget)} and + * {@link #changeVariables(Object, java.util.Map)} just like in Vaadin 6. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public interface Vaadin6Component extends VariableOwner, Component, + EventListener { + + /** + * <p> + * Paints the Paintable into a UIDL stream. This method creates the UIDL + * sequence describing it and outputs it to the given UIDL stream. + * </p> + * + * <p> + * It is called when the contents of the component should be painted in + * response to the component first being shown or having been altered so + * that its visual representation is changed. + * </p> + * + * @param target + * the target UIDL stream where the component should paint itself + * to. + * @throws PaintException + * if the paint operation failed. + */ + public void paintContent(PaintTarget target) throws PaintException; + +} diff --git a/src/com/vaadin/terminal/VariableOwner.java b/src/com/vaadin/terminal/VariableOwner.java index 49e2179ece..c52e04c008 100644 --- a/src/com/vaadin/terminal/VariableOwner.java +++ b/src/com/vaadin/terminal/VariableOwner.java @@ -20,7 +20,10 @@ import java.util.Map; * @version * @VERSION@ * @since 3.0 + * @deprecated in 7.0. Only provided to ease porting of Vaadin 6 components. Do + * not implement this directly, implement {@link Vaadin6Component}. */ +@Deprecated public interface VariableOwner extends Serializable { /** diff --git a/src/com/vaadin/terminal/WrappedRequest.java b/src/com/vaadin/terminal/WrappedRequest.java new file mode 100644 index 0000000000..a27213d921 --- /dev/null +++ b/src/com/vaadin/terminal/WrappedRequest.java @@ -0,0 +1,277 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Locale; +import java.util.Map; + +import javax.portlet.PortletRequest; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import com.vaadin.Application; +import com.vaadin.RootRequiresMoreInformationException; +import com.vaadin.annotations.EagerInit; +import com.vaadin.terminal.gwt.server.WebBrowser; +import com.vaadin.ui.Root; + +/** + * A generic request to the server, wrapping a more specific request type, e.g. + * HttpServletReqest or PortletRequest. + * + * @since 7.0 + */ +public interface WrappedRequest extends Serializable { + + /** + * Detailed information extracted from the browser. + * + * @see WrappedRequest#getBrowserDetails() + */ + public interface BrowserDetails extends Serializable { + /** + * Gets the URI hash fragment for the request. This is typically used to + * encode navigation within an application. + * + * @return the URI hash fragment + */ + public String getUriFragment(); + + /** + * Gets the value of window.name from the browser. This can be used to + * keep track of the specific window between browser reloads. + * + * @return the string value of window.name in the browser + */ + public String getWindowName(); + + /** + * Gets a reference to the {@link WebBrowser} object containing + * additional information, e.g. screen size and the time zone offset. + * + * @return the web browser object + */ + public WebBrowser getWebBrowser(); + } + + /** + * Gets the named request parameter This is typically a HTTP GET or POST + * parameter, though other request types might have other ways of + * representing parameters. + * + * @see javax.servlet.ServletRequest#getParameter(String) + * @see javax.portlet.PortletRequest#getParameter(String) + * + * @param parameter + * the name of the parameter + * @return The paramter value, or <code>null</code> if no parameter with the + * given name is present + */ + public String getParameter(String parameter); + + /** + * Gets all the parameters of the request. + * + * @see #getParameter(String) + * + * @see javax.servlet.ServletRequest#getParameterMap() + * @see javax.portlet.PortletRequest#getParameter(String) + * + * @return A mapping of parameter names to arrays of parameter values + */ + public Map<String, String[]> getParameterMap(); + + /** + * Returns the length of the request content that can be read from the input + * stream returned by {@link #getInputStream()}. + * + * @see javax.servlet.ServletRequest#getContentLength() + * @see javax.portlet.ClientDataRequest#getContentLength() + * + * @return content length in bytes + */ + public int getContentLength(); + + /** + * Returns an input stream from which the request content can be read. The + * request content length can be obtained with {@link #getContentLength()} + * without reading the full stream contents. + * + * @see javax.servlet.ServletRequest#getInputStream() + * @see javax.portlet.ClientDataRequest#getPortletInputStream() + * + * @return the input stream from which the contents of the request can be + * read + * @throws IOException + * if the input stream can not be opened + */ + public InputStream getInputStream() throws IOException; + + /** + * Gets a request attribute. + * + * @param name + * the name of the attribute + * @return the value of the attribute, or <code>null</code> if there is no + * attribute with the given name + * + * @see javax.servlet.ServletRequest#getAttribute(String) + * @see javax.portlet.PortletRequest#getAttribute(String) + */ + public Object getAttribute(String name); + + /** + * Defines a request attribute. + * + * @param name + * the name of the attribute + * @param value + * the attribute value + * + * @see javax.servlet.ServletRequest#setAttribute(String, Object) + * @see javax.portlet.PortletRequest#setAttribute(String, Object) + */ + public void setAttribute(String name, Object value); + + /** + * Gets the path of the requested resource relative to the application. The + * path be <code>null</code> if no path information is available. Does + * always start with / if the path isn't <code>null</code>. + * + * @return a string with the path relative to the application. + * + * @see javax.servlet.http.HttpServletRequest#getPathInfo() + */ + public String getRequestPathInfo(); + + /** + * Returns the maximum time interval, in seconds, that the session + * associated with this request will be kept open between client accesses. + * + * @return an integer specifying the number of seconds the session + * associated with this request remains open between client requests + * + * @see javax.servlet.http.HttpSession#getMaxInactiveInterval() + * @see javax.portlet.PortletSession#getMaxInactiveInterval() + */ + public int getSessionMaxInactiveInterval(); + + /** + * Gets an attribute from the session associated with this request. + * + * @param name + * the name of the attribute + * @return the attribute value, or <code>null</code> if the attribute is not + * defined in the session + * + * @see javax.servlet.http.HttpSession#getAttribute(String) + * @see javax.portlet.PortletSession#getAttribute(String) + */ + public Object getSessionAttribute(String name); + + /** + * Saves an attribute value in the session associated with this request. + * + * @param name + * the name of the attribute + * @param attribute + * the attribute value + * + * @see javax.servlet.http.HttpSession#setAttribute(String, Object) + * @see javax.portlet.PortletSession#setAttribute(String, Object) + */ + public void setSessionAttribute(String name, Object attribute); + + /** + * Returns the MIME type of the body of the request, or null if the type is + * not known. + * + * @return a string containing the name of the MIME type of the request, or + * null if the type is not known + * + * @see javax.servlet.ServletRequest#getContentType() + * @see javax.portlet.ResourceRequest#getContentType() + * + */ + public String getContentType(); + + /** + * Gets detailed information about the browser from which the request + * originated. This consists of information that is not available from + * normal HTTP requests, but requires additional information to be extracted + * for instance using javascript in the browser. + * + * This information is only guaranteed to be available in some special + * cases, for instance when {@link Application#getRoot} is called again + * after throwing {@link RootRequiresMoreInformationException} or in + * {@link Root#init(WrappedRequest)} for a Root class not annotated with + * {@link EagerInit} + * + * @return the browser details, or <code>null</code> if details are not + * available + * + * @see BrowserDetails + */ + public BrowserDetails getBrowserDetails(); + + /** + * Gets locale information from the query, e.g. using the Accept-Language + * header. + * + * @return the preferred Locale + * + * @see ServletRequest#getLocale() + * @see PortletRequest#getLocale() + */ + public Locale getLocale(); + + /** + * Returns the IP address from which the request came. This might also be + * the address of a proxy between the server and the original requester. + * + * @return a string containing the IP address, or <code>null</code> if the + * address is not available + * + * @see ServletRequest#getRemoteAddr() + */ + public String getRemoteAddr(); + + /** + * Checks whether the request was made using a secure channel, e.g. using + * https. + * + * @return a boolean indicating if the request is secure + * + * @see ServletRequest#isSecure() + * @see PortletRequest#isSecure() + */ + public boolean isSecure(); + + /** + * Gets the value of a request header, e.g. a http header for a + * {@link HttpServletRequest}. + * + * @param headerName + * the name of the header + * @return the header value, or <code>null</code> if the header is not + * present in the request + * + * @see HttpServletRequest#getHeader(String) + */ + public String getHeader(String headerName); + + /** + * Gets the deployment configuration for the context of this request. + * + * @return the deployment configuration + * + * @see DeploymentConfiguration + */ + public DeploymentConfiguration getDeploymentConfiguration(); + +} diff --git a/src/com/vaadin/terminal/WrappedResponse.java b/src/com/vaadin/terminal/WrappedResponse.java new file mode 100644 index 0000000000..995133a269 --- /dev/null +++ b/src/com/vaadin/terminal/WrappedResponse.java @@ -0,0 +1,147 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Serializable; + +import javax.portlet.MimeResponse; +import javax.portlet.PortletResponse; +import javax.portlet.ResourceResponse; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +/** + * A generic response from the server, wrapping a more specific response type, + * e.g. HttpServletResponse or PortletResponse. + * + * @since 7.0 + */ +public interface WrappedResponse extends Serializable { + + /** + * Sets the (http) status code for the response. If you want to include an + * error message along the status code, use {@link #sendError(int, String)} + * instead. + * + * @param statusCode + * the status code to set + * @see HttpServletResponse#setStatus(int) + * + * @see ResourceResponse#HTTP_STATUS_CODE + */ + public void setStatus(int statusCode); + + /** + * Sets the content type of this response. If the content type including a + * charset is set before {@link #getWriter()} is invoked, the returned + * PrintWriter will automatically use the defined charset. + * + * @param contentType + * a string specifying the MIME type of the content + * + * @see ServletResponse#setContentType(String) + * @see MimeResponse#setContentType(String) + */ + public void setContentType(String contentType); + + /** + * Sets the value of a generic response header. If the header had already + * been set, the new value overwrites the previous one. + * + * @param name + * the name of the header + * @param value + * the header value. + * + * @see HttpServletResponse#setHeader(String, String) + * @see PortletResponse#setProperty(String, String) + */ + public void setHeader(String name, String value); + + /** + * Properly formats a timestamp as a date header. If the header had already + * been set, the new value overwrites the previous one. + * + * @param name + * the name of the header + * @param timestamp + * the number of milliseconds since epoch + * + * @see HttpServletResponse#setDateHeader(String, long) + */ + public void setDateHeader(String name, long timestamp); + + /** + * Returns a <code>OutputStream</code> for writing binary data in the + * response. + * <p> + * Either this method or getWriter() may be called to write the response, + * not both. + * + * @return a <code>OutputStream</code> for writing binary data + * @throws IOException + * if an input or output exception occurred + * + * @see #getWriter() + * @see ServletResponse#getOutputStream() + * @see MimeResponse#getPortletOutputStream() + */ + public OutputStream getOutputStream() throws IOException; + + /** + * Returns a <code>PrintWriter</code> object that can send character text to + * the client. The PrintWriter uses the character encoding defined using + * setContentType. + * <p> + * Either this method or getOutputStream() may be called to write the + * response, not both. + * + * @return a <code>PrintWriter</code> for writing character text + * @throws IOException + * if an input or output exception occurred + * + * @see #getOutputStream() + * @see ServletResponse#getWriter() + * @see MimeResponse#getWriter() + */ + public PrintWriter getWriter() throws IOException; + + /** + * Sets cache time in milliseconds, -1 means no cache at all. All required + * headers related to caching in the response are set based on the time. + * + * @param milliseconds + * Cache time in milliseconds + */ + public void setCacheTime(long milliseconds); + + /** + * Sends an error response to the client using the specified status code and + * clears the buffer. In some configurations, this can cause a predefined + * error page to be displayed. + * + * @param errorCode + * the HTTP status code + * @param message + * a message to accompany the error + * @throws IOException + * if an input or output exception occurs + * + * @see HttpServletResponse#sendError(int, String) + */ + public void sendError(int errorCode, String message) throws IOException; + + /** + * Gets the deployment configuration for the context of this response. + * + * @return the deployment configuration + * + * @see DeploymentConfiguration + */ + public DeploymentConfiguration getDeploymentConfiguration(); +} diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml index cebfd8a679..f65b4c51e7 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -1,6 +1,6 @@ <module> - <!-- This GWT module defines the Vaadin DefaultWidgetSet. This is the module - you want to extend when creating an extended widget set, or when creating + <!-- This GWT module defines the Vaadin DefaultWidgetSet. This is the module + you want to extend when creating an extended widget set, or when creating a specialized widget set with a subset of the components. --> <!-- Hint for WidgetSetBuilder not to automatically update the file --> @@ -10,26 +10,34 @@ <inherits name="com.google.gwt.http.HTTP" /> + <inherits name="com.google.gwt.json.JSON" /> + + <inherits + name="com.vaadin.terminal.gwt.DefaultWidgetSetBrowserSpecificOverrides" /> + <source path="client" /> - <!-- Use own Scheduler implementation to be able to track if commands are + <!-- Use own Scheduler implementation to be able to track if commands are running --> <replace-with class="com.vaadin.terminal.gwt.client.VSchedulerImpl"> <when-type-is class="com.google.gwt.core.client.impl.SchedulerImpl" /> </replace-with> - + + + <!-- Generators for serializators for classes used in communication between + server and client --> + <generate-with + class="com.vaadin.terminal.gwt.widgetsetutils.SerializerMapGenerator"> + <when-type-is + class="com.vaadin.terminal.gwt.client.communication.SerializerMap" /> + </generate-with> + <replace-with class="com.vaadin.terminal.gwt.client.VDebugConsole"> <when-type-is class="com.vaadin.terminal.gwt.client.Console" /> </replace-with> - <!-- Use our own history impl for IE to workaround #2931. --> - <replace-with class="com.vaadin.terminal.gwt.client.HistoryImplIEVaadin"> - <when-type-is class="com.google.gwt.user.client.impl.HistoryImpl" /> - <when-property-is name="user.agent" value="ie6" /> - </replace-with> - <generate-with - class="com.vaadin.terminal.gwt.widgetsetutils.EagerWidgetMapGenerator"> + class="com.vaadin.terminal.gwt.widgetsetutils.EagerWidgetMapGenerator"> <when-type-is class="com.vaadin.terminal.gwt.client.WidgetMap" /> </generate-with> @@ -39,47 +47,31 @@ class="com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterionFactory" /> </generate-with> - <!-- Fall through to this rule for everything but IE --> - <replace-with - class="com.vaadin.terminal.gwt.client.ui.UploadIFrameOnloadStrategy"> - <when-type-is - class="com.vaadin.terminal.gwt.client.ui.UploadIFrameOnloadStrategy" /> - </replace-with> - - <replace-with - class="com.vaadin.terminal.gwt.client.ui.UploadIFrameOnloadStrategyIE"> - <when-type-is - class="com.vaadin.terminal.gwt.client.ui.UploadIFrameOnloadStrategy" /> - <any> - <when-property-is name="user.agent" value="ie6" /> - <when-property-is name="user.agent" value="ie8" /> - </any> - </replace-with> + <!-- Generate client side proxies for client to server RPC interfaces --> + <generate-with + class="com.vaadin.terminal.gwt.widgetsetutils.RpcProxyGenerator"> + <when-type-assignable + class="com.vaadin.terminal.gwt.client.communication.ServerRpc" /> + </generate-with> - <!-- Fall through to this rule for everything but IE --> - <replace-with - class="com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper"> - <when-type-is - class="com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper" /> - </replace-with> + <!-- Generate client side proxies for client to server RPC interfaces --> + <generate-with + class="com.vaadin.terminal.gwt.widgetsetutils.RpcProxyCreatorGenerator"> + <when-type-assignable + class="com.vaadin.terminal.gwt.client.communication.RpcProxy.RpcProxyCreator" /> + </generate-with> - <replace-with - class="com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapperIE"> - <when-type-is - class="com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper" /> - <any> - <when-property-is name="user.agent" value="ie6" /> - <when-property-is name="user.agent" value="ie8" /> - </any> - </replace-with> - - <!-- Workaround for #6682. Remove when fixed in GWT. --> - <replace-with class="com.google.gwt.dom.client.VaadinDOMImplSafari"> - <when-type-is class="com.google.gwt.dom.client.DOMImpl" /> - <when-property-is name="user.agent" value="safari" /> - </replace-with> + <!-- Generate client side RPC manager for server to client RPC --> + <generate-with + class="com.vaadin.terminal.gwt.widgetsetutils.RpcManagerGenerator"> + <when-type-assignable + class="com.vaadin.terminal.gwt.client.communication.RpcManager" /> + </generate-with> <entry-point class="com.vaadin.terminal.gwt.client.ApplicationConfiguration" /> + <!-- Use the new cross site linker to get a nocache.js without document.write --> + <add-linker name="xsiframe" /> + </module> diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSetBrowserSpecificOverrides.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSetBrowserSpecificOverrides.gwt.xml new file mode 100644 index 0000000000..b5ab61df64 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSetBrowserSpecificOverrides.gwt.xml @@ -0,0 +1,53 @@ +<module> + <!-- This GWT module defines the browser specific overrides used by Vaadin --> + + <!-- Hint for WidgetSetBuilder not to automatically update the file --> + <!-- WS Compiler: manually edited --> + + <!-- Fall through to this rule for everything but IE --> + <replace-with + class="com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategy"> + <when-type-is + class="com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategy" /> + </replace-with> + + <replace-with + class="com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategyIE"> + <when-type-is + class="com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategy" /> + <any> + <when-property-is name="user.agent" value="ie8" /> + </any> + </replace-with> + + <!-- Fall through to this rule for everything but IE --> + <replace-with class="com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper" /> + </replace-with> + + <replace-with class="com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapperIE"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper" /> + <any> + <when-property-is name="user.agent" value="ie8" /> + </any> + </replace-with> + + <!-- Fall through to this rule for everything but IE --> + <replace-with class="com.vaadin.terminal.gwt.client.LayoutManager"> + <when-type-is class="com.vaadin.terminal.gwt.client.LayoutManager" /> + </replace-with> + + <replace-with class="com.vaadin.terminal.gwt.client.LayoutManagerIE8"> + <when-type-is class="com.vaadin.terminal.gwt.client.LayoutManager" /> + <any> + <when-property-is name="user.agent" value="ie8" /> + </any> + </replace-with> + + <!-- Workaround for #6682. Remove when fixed in GWT. --> + <replace-with class="com.google.gwt.dom.client.VaadinDOMImplSafari"> + <when-type-is class="com.google.gwt.dom.client.DOMImpl" /> + <when-property-is name="user.agent" value="safari" /> + </replace-with> + +</module> diff --git a/src/com/vaadin/terminal/gwt/client/AbstractFieldState.java b/src/com/vaadin/terminal/gwt/client/AbstractFieldState.java new file mode 100644 index 0000000000..3a66a01f23 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/AbstractFieldState.java @@ -0,0 +1,137 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import com.vaadin.terminal.gwt.client.ui.TabIndexState; +import com.vaadin.ui.AbstractField; + +/** + * Shared state for {@link AbstractField}. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class AbstractFieldState extends ComponentState implements TabIndexState { + private boolean propertyReadOnly = false; + private boolean hideErrors = false; + private boolean required = false; + private boolean modified = false; + + /** + * The tab order number of this field. + */ + private int tabIndex = 0; + + /** + * Checks if the property data source for the Field is in read only mode. + * This affects the read only state of the field itself. + * + * @return true if there is a property data source and it is set to read + * only, false otherwise + */ + public boolean isPropertyReadOnly() { + return propertyReadOnly; + } + + /** + * Sets the read only state of the property data source. + * + * @param propertyReadOnly + * true if the property data source if read only, false otherwise + */ + public void setPropertyReadOnly(boolean propertyReadOnly) { + this.propertyReadOnly = propertyReadOnly; + } + + /** + * Returns true if the component will hide any errors even if the error + * message is set. + * + * @return true if error messages are disabled + */ + public boolean isHideErrors() { + return hideErrors; + } + + /** + * Sets whether the component should hide any errors even if the error + * message is set. + * + * This is used e.g. on forms to hide error messages for invalid fields + * before the first user actions. + * + * @param hideErrors + * true if error messages should be hidden + */ + public void setHideErrors(boolean hideErrors) { + this.hideErrors = hideErrors; + } + + /** + * Is the field required. Required fields must filled by the user. + * + * See AbstractField#isRequired() for more information. + * + * @return <code>true</code> if the field is required, otherwise + * <code>false</code>. + */ + public boolean isRequired() { + return required; + } + + /** + * Sets the field required. Required fields must filled by the user. + * + * See AbstractField#setRequired(boolean) for more information. + * + * @param required + * Is the field required. + */ + public void setRequired(boolean required) { + this.required = required; + } + + /** + * Has the contents of the field been modified, i.e. has the value been + * updated after it was read from the data source. + * + * @return true if the field has been modified, false otherwise + */ + public boolean isModified() { + return modified; + } + + /** + * Setter for the modified flag, toggled when the contents of the field is + * modified by the user. + * + * @param modified + * the new modified state + * + */ + public void setModified(boolean modified) { + this.modified = modified; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ComponentState#getTabIndex() + */ + public int getTabIndex() { + return tabIndex; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ui.TabIndexState#setTabIndex(int) + */ + public void setTabIndex(int tabIndex) { + this.tabIndex = tabIndex; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java index a60fb808a1..170e949116 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java @@ -5,22 +5,180 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Timer; -import com.vaadin.terminal.gwt.client.ui.VUnknownComponent; +import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class ApplicationConfiguration implements EntryPoint { /** + * Helper class for reading configuration options from the bootstap + * javascript + * + * @since 7.0 + */ + private static class JsoConfiguration extends JavaScriptObject { + protected JsoConfiguration() { + // JSO Constructor + } + + /** + * Reads a configuration parameter as a string. Please note that the + * javascript value of the parameter should also be a string, or else an + * undefined exception may be thrown. + * + * @param name + * name of the configuration parameter + * @return value of the configuration parameter, or <code>null</code> if + * not defined + */ + private native String getConfigString(String name) + /*-{ + var value = this.getConfig(name); + if (value === null || value === undefined) { + return null; + } else { + return value +""; + } + }-*/; + + /** + * Reads a configuration parameter as a boolean object. Please note that + * the javascript value of the parameter should also be a boolean, or + * else an undefined exception may be thrown. + * + * @param name + * name of the configuration parameter + * @return boolean value of the configuration paramter, or + * <code>null</code> if no value is defined + */ + private native Boolean getConfigBoolean(String name) + /*-{ + var value = this.getConfig(name); + if (value === null || value === undefined) { + return null; + } else { + return @java.lang.Boolean::valueOf(Z)(value); + } + }-*/; + + /** + * Reads a configuration parameter as an integer object. Please note + * that the javascript value of the parameter should also be an integer, + * or else an undefined exception may be thrown. + * + * @param name + * name of the configuration parameter + * @return integer value of the configuration paramter, or + * <code>null</code> if no value is defined + */ + private native Integer getConfigInteger(String name) + /*-{ + var value = this.getConfig(name); + if (value === null || value === undefined) { + return null; + } else { + return @java.lang.Integer::valueOf(I)(value); + } + }-*/; + + /** + * Reads a configuration parameter as an {@link ErrorMessage} object. + * Please note that the javascript value of the parameter should also be + * an object with appropriate fields, or else an undefined exception may + * be thrown when calling this method or when calling methods on the + * returned object. + * + * @param name + * name of the configuration parameter + * @return error message with the given name, or <code>null</code> if no + * value is defined + */ + private native ErrorMessage getConfigError(String name) + /*-{ + return this.getConfig(name); + }-*/; + + /** + * Returns a native javascript object containing version information + * from the server. + * + * @return a javascript object with the version information + */ + private native JavaScriptObject getVersionInfoJSObject() + /*-{ + return this.getConfig("versionInfo"); + }-*/; + + /** + * Gets the version of the Vaadin framework used on the server. + * + * @return a string with the version + * + * @see com.vaadin.terminal.gwt.server.AbstractApplicationServlet#VERSION + */ + private native String getVaadinVersion() + /*-{ + return this.getConfig("versionInfo").vaadinVersion; + }-*/; + + /** + * Gets the version of the application running on the server. + * + * @return a string with the application version + * + * @see com.vaadin.Application#getVersion() + */ + private native String getApplicationVersion() + /*-{ + return this.getConfig("versionInfo").applicationVersion; + }-*/; + + private native String getUIDL() + /*-{ + return this.getConfig("uidl"); + }-*/; + } + + /** + * Wraps a native javascript object containing fields for an error message + * + * @since 7.0 + */ + public static final class ErrorMessage extends JavaScriptObject { + + protected ErrorMessage() { + // JSO constructor + } + + public final native String getCaption() + /*-{ + return this.caption; + }-*/; + + public final native String getMessage() + /*-{ + return this.message; + }-*/; + + public final native String getUrl() + /*-{ + return this.url; + }-*/; + } + + /** * Builds number. For example 0-custom_tag in 5.0.0-custom_tag. */ public static final String VERSION; @@ -39,34 +197,30 @@ public class ApplicationConfiguration implements EntryPoint { private String id; private String themeUri; private String appUri; - private JavaScriptObject versionInfo; - private String windowName; + private int rootId; private boolean standalone; - private String communicationErrorCaption; - private String communicationErrorMessage; - private String communicationErrorUrl; - private String authorizationErrorCaption; - private String authorizationErrorMessage; - private String authorizationErrorUrl; - private String requiredWidgetset; + private ErrorMessage communicationError; + private ErrorMessage authorizationError; private boolean useDebugIdInDom = true; private boolean usePortletURLs = false; private String portletUidlURLBase; - private HashMap<String, String> unknownComponents; + private HashMap<Integer, String> unknownComponents; - private Class<? extends Paintable>[] classes = new Class[1024]; + private Class<? extends ComponentConnector>[] classes = new Class[1024]; - private String windowId; + private boolean browserDetailsSent = false; static// TODO consider to make this hashmap per application LinkedList<Command> callbacks = new LinkedList<Command>(); private static int widgetsLoading; - private static ArrayList<ApplicationConnection> unstartedApplications = new ArrayList<ApplicationConnection>(); private static ArrayList<ApplicationConnection> runningApplications = new ArrayList<ApplicationConnection>(); + private Map<Integer, Integer> componentInheritanceMap = new HashMap<Integer, Integer>(); + private Map<Integer, String> tagToServerSideClassName = new HashMap<Integer, String>(); + public boolean usePortletURLs() { return usePortletURLs; } @@ -89,6 +243,13 @@ public class ApplicationConfiguration implements EntryPoint { return appUri; } + public String getThemeName() { + String uri = getThemeUri(); + String themeName = uri.substring(uri.lastIndexOf('/')); + themeName = themeName.replaceAll("[^a-zA-Z0-9]", ""); + return themeName; + } + public String getThemeUri() { return themeUri; } @@ -98,6 +259,16 @@ public class ApplicationConfiguration implements EntryPoint { } /** + * Gets the initial UIDL from the DOM, if it was provided during the init + * process. + * + * @return + */ + public String getUIDL() { + return getJsoConfiguration(id).getUIDL(); + } + + /** * @return true if the application is served by std. Vaadin servlet and is * considered to be the only or main content of the host page. */ @@ -105,174 +276,99 @@ public class ApplicationConfiguration implements EntryPoint { return standalone; } - public void setInitialWindowName(String name) { - windowName = name; - } - - public String getInitialWindowName() { - return windowName; + /** + * Gets the root if of this application instance. The root id should be + * included in every request originating from this instance in order to + * associate it with the right Root instance on the server. + * + * @return the root id + */ + public int getRootId() { + return rootId; } public JavaScriptObject getVersionInfoJSObject() { - return versionInfo; + return getJsoConfiguration(id).getVersionInfoJSObject(); } - public String getCommunicationErrorCaption() { - return communicationErrorCaption; + public ErrorMessage getCommunicationError() { + return communicationError; } - public String getCommunicationErrorMessage() { - return communicationErrorMessage; + public ErrorMessage getAuthorizationError() { + return authorizationError; } - public String getCommunicationErrorUrl() { - return communicationErrorUrl; - } + /** + * Reads the configuration values defined by the bootstrap javascript. + */ + private void loadFromDOM() { + JsoConfiguration jsoConfiguration = getJsoConfiguration(id); + appUri = jsoConfiguration.getConfigString("appUri"); + if (appUri != null && !appUri.endsWith("/")) { + appUri += '/'; + } + themeUri = jsoConfiguration.getConfigString("themeUri"); + rootId = jsoConfiguration.getConfigInteger("rootId").intValue(); - public String getAuthorizationErrorCaption() { - return authorizationErrorCaption; - } + // null -> true + useDebugIdInDom = jsoConfiguration.getConfigBoolean("useDebugIdInDom") != Boolean.FALSE; - public String getAuthorizationErrorMessage() { - return authorizationErrorMessage; - } + // null -> false + usePortletURLs = jsoConfiguration.getConfigBoolean("usePortletURLs") == Boolean.TRUE; - public String getAuthorizationErrorUrl() { - return authorizationErrorUrl; - } + portletUidlURLBase = jsoConfiguration + .getConfigString("portletUidlURLBase"); - public String getRequiredWidgetset() { - return requiredWidgetset; - } + // null -> false + standalone = jsoConfiguration.getConfigBoolean("standalone") == Boolean.TRUE; - private native void loadFromDOM() - /*-{ + communicationError = jsoConfiguration.getConfigError("comErrMsg"); + authorizationError = jsoConfiguration.getConfigError("authErrMsg"); - var id = this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::id; - if($wnd.vaadin.vaadinConfigurations && $wnd.vaadin.vaadinConfigurations[id]) { - var jsobj = $wnd.vaadin.vaadinConfigurations[id]; - var uri = jsobj.appUri; - if(uri != null && uri[uri.length -1] != "/") { - uri = uri + "/"; - } - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::appUri = uri; - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::themeUri = jsobj.themeUri; - if(jsobj.windowName) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::windowName = jsobj.windowName; - } - if('useDebugIdInDom' in jsobj && typeof(jsobj.useDebugIdInDom) == "boolean") { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::useDebugIdInDom = jsobj.useDebugIdInDom; - } - if(jsobj.versionInfo) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::versionInfo = jsobj.versionInfo; - } - if(jsobj.comErrMsg) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::communicationErrorCaption = jsobj.comErrMsg.caption; - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::communicationErrorMessage = jsobj.comErrMsg.message; - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::communicationErrorUrl = jsobj.comErrMsg.url; - } - if(jsobj.authErrMsg) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::authorizationErrorCaption = jsobj.authErrMsg.caption; - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::authorizationErrorMessage = jsobj.authErrMsg.message; - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::authorizationErrorUrl = jsobj.authErrMsg.url; - } - if (jsobj.usePortletURLs) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::usePortletURLs = jsobj.usePortletURLs; - } - if (jsobj.portletUidlURLBase) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::portletUidlURLBase = jsobj.portletUidlURLBase; - } - if (jsobj.standalone) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::standalone = true; - } - if (jsobj.widgetset) { - this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::requiredWidgetset = jsobj.widgetset; - } - } else { - $wnd.alert("Vaadin app failed to initialize: " + this.id); + // boostrap sets initPending to false if it has sent the browser details + if (jsoConfiguration.getConfigBoolean("initPending") == Boolean.FALSE) { + setBrowserDetailsSent(); } - }-*/; + } /** - * Inits the ApplicationConfiguration by reading the DOM and instantiating - * ApplicationConnections accordingly. Call {@link #startNextApplication()} - * to actually start the applications. + * Starts the application with a given id by reading the configuration + * options stored by the bootstrap javascript. * - * @param widgetset - * the widgetset that is running the apps + * @param applicationId + * id of the application to load, this is also the id of the html + * element into which the application should be rendered. */ - public static void initConfigurations() { - - ArrayList<String> appIds = new ArrayList<String>(); - loadAppIdListFromDOM(appIds); - - for (Iterator<String> it = appIds.iterator(); it.hasNext();) { - String appId = it.next(); - ApplicationConfiguration appConf = getConfigFromDOM(appId); - if (canStartApplication(appConf)) { + public static void startApplication(final String applicationId) { + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + public void execute() { + ApplicationConfiguration appConf = getConfigFromDOM(applicationId); ApplicationConnection a = GWT .create(ApplicationConnection.class); a.init(widgetSet, appConf); - unstartedApplications.add(a); - consumeApplication(appId); - } else { - VConsole.log("Application " - + appId - + " was not started. Provided widgetset did not match with this module."); + a.start(); + runningApplications.add(a); } - } - + }); } - /** - * Marks an applicatin with given id to be initialized. Suggesting other - * modules should not try to start this application anymore. - * - * @param appId - */ - private native static void consumeApplication(String appId) - /*-{ - $wnd.vaadin.vaadinConfigurations[appId].initialized = true; - }-*/; - - private static boolean canStartApplication(ApplicationConfiguration appConf) { - return appConf.getRequiredWidgetset() == null - || appConf.getRequiredWidgetset().equals(GWT.getModuleName()); + public static List<ApplicationConnection> getRunningApplications() { + return runningApplications; } /** - * Starts the next unstarted application. The WidgetSet should call this - * once to start the first application; after that, each application should - * call this once it has started. This ensures that the applications are - * started synchronously, which is neccessary to avoid session-id problems. + * Gets the configuration object for a specific application from the + * bootstrap javascript. * - * @return true if an unstarted application was found + * @param appId + * the id of the application to get configuration data for + * @return a native javascript object containing the configuration data */ - public static boolean startNextApplication() { - if (unstartedApplications.size() > 0) { - ApplicationConnection a = unstartedApplications.remove(0); - a.start(); - runningApplications.add(a); - return true; - } else { - deferredWidgetLoader = new DeferredWidgetLoader(); - return false; - } - } - - public static List<ApplicationConnection> getRunningApplications() { - return runningApplications; - } - - private native static void loadAppIdListFromDOM(ArrayList<String> list) + private native static JsoConfiguration getJsoConfiguration(String appId) /*-{ - var j; - for(j in $wnd.vaadin.vaadinConfigurations) { - if(!$wnd.vaadin.vaadinConfigurations[j].initialized) { - list.@java.util.Collection::add(Ljava/lang/Object;)(j); - } - } + return $wnd.vaadin.getApp(appId); }-*/; public static ApplicationConfiguration getConfigFromDOM(String appId) { @@ -282,27 +378,34 @@ public class ApplicationConfiguration implements EntryPoint { return conf; } - public native String getServletVersion() - /*-{ - return this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::versionInfo.vaadinVersion; - }-*/; + public String getServletVersion() { + return getJsoConfiguration(id).getVaadinVersion(); + } - public native String getApplicationVersion() - /*-{ - return this.@com.vaadin.terminal.gwt.client.ApplicationConfiguration::versionInfo.applicationVersion; - }-*/; + public String getApplicationVersion() { + return getJsoConfiguration(id).getApplicationVersion(); + } public boolean useDebugIdInDOM() { return useDebugIdInDom; } - public Class<? extends Paintable> getWidgetClassByEncodedTag(String tag) { + public Class<? extends ComponentConnector> getWidgetClassByEncodedTag( + int tag) { try { - int parseInt = Integer.parseInt(tag); - return classes[parseInt]; + return classes[tag]; } catch (Exception e) { // component was not present in mappings - return VUnknownComponent.class; + return UnknownComponentConnector.class; + } + } + + public void addComponentInheritanceInfo(ValueMap valueMap) { + JsArrayString keyArray = valueMap.getKeyArray(); + for (int i = 0; i < keyArray.length(); i++) { + String key = keyArray.get(i); + int value = valueMap.getInt(key); + componentInheritanceMap.put(Integer.parseInt(key), value); } } @@ -311,27 +414,31 @@ public class ApplicationConfiguration implements EntryPoint { for (int i = 0; i < keyArray.length(); i++) { String key = keyArray.get(i).intern(); int value = valueMap.getInt(key); - classes[value] = widgetSet.getImplementationByClassName(key); - if (classes[value] == VUnknownComponent.class) { + tagToServerSideClassName.put(value, key); + } + + for (int i = 0; i < keyArray.length(); i++) { + String key = keyArray.get(i).intern(); + int value = valueMap.getInt(key); + classes[value] = widgetSet.getConnectorClassByTag(value, this); + if (classes[value] == UnknownComponentConnector.class) { if (unknownComponents == null) { - unknownComponents = new HashMap<String, String>(); + unknownComponents = new HashMap<Integer, String>(); } - unknownComponents.put("" + value, key); - } else if (key == "com.vaadin.ui.Window") { - windowId = "" + value; + unknownComponents.put(value, key); } } } - /** - * @return the integer value that is used to code top level windows - * "com.vaadin.ui.Window" - */ - String getEncodedWindowTag() { - return windowId; + public Integer getParentTag(int tag) { + return componentInheritanceMap.get(tag); } - String getUnknownServerClassNameByEncodedTagName(String tag) { + public String getServerSideClassNameForTag(Integer tag) { + return tagToServerSideClassName.get(tag); + } + + String getUnknownServerClassNameByTag(int tag) { if (unknownComponents != null) { return unknownComponents.get(tag); } @@ -398,7 +505,7 @@ public class ApplicationConfiguration implements EntryPoint { public void run() { pending = false; if (!isBusy()) { - Class<? extends Paintable> nextType = getNextType(); + Class<? extends ComponentConnector> nextType = getNextType(); if (nextType == null) { // ensured that all widgets are loaded deferredWidgetLoader = null; @@ -411,8 +518,8 @@ public class ApplicationConfiguration implements EntryPoint { } } - private Class<? extends Paintable> getNextType() { - Class<? extends Paintable>[] deferredLoadedWidgets = widgetSet + private Class<? extends ComponentConnector> getNextType() { + Class<? extends ComponentConnector>[] deferredLoadedWidgets = widgetSet .getDeferredLoadedWidgets(); if (deferredLoadedWidgets.length <= nextWidgetIndex) { return null; @@ -443,10 +550,6 @@ public class ApplicationConfiguration implements EntryPoint { public void onModuleLoad() { - // Enable IE6 Background image caching - if (BrowserInfo.get().isIE6()) { - enableIE6BackgroundImageCache(); - } // Prepare VConsole for debugging if (isDebugMode()) { Console console = GWT.create(Console.class); @@ -472,21 +575,22 @@ public class ApplicationConfiguration implements EntryPoint { } }); - initConfigurations(); - startNextApplication(); + registerCallback(GWT.getModuleName()); + deferredWidgetLoader = new DeferredWidgetLoader(); } - // From ImageSrcIE6 - private static native void enableIE6BackgroundImageCache() + /** + * Registers that callback that the bootstrap javascript uses to start + * applications once the widgetset is loaded and all required information is + * available + * + * @param widgetsetName + * the name of this widgetset + */ + public native static void registerCallback(String widgetsetName) /*-{ - // Fix IE background image refresh bug, present through IE6 - // see http://www.mister-pixel.com/#Content__state=is_that_simple - // this only works with IE6 SP1+ - try { - $doc.execCommand("BackgroundImageCache", false, true); - } catch (e) { - // ignore error on other browsers - } + var callbackHandler = @com.vaadin.terminal.gwt.client.ApplicationConfiguration::startApplication(Ljava/lang/String;); + $wnd.vaadin.registerWidgetset(widgetsetName, callbackHandler); }-*/; /** @@ -518,4 +622,25 @@ public class ApplicationConfiguration implements EntryPoint { return re.test(uri); }-*/; + /** + * Checks whether information from the web browser (e.g. uri fragment and + * screen size) has been sent to the server. + * + * @return <code>true</code> if browser information has already been sent + * + * @see ApplicationConnection#getNativeBrowserDetailsParameters(String) + */ + public boolean isBrowserDetailsSent() { + return browserDetailsSent; + } + + /** + * Registers that the browser details have been sent. + * {@link #isBrowserDetailsSent()} will return + * <code> after this method has been invoked. + */ + public void setBrowserDetailsSent() { + browserDetailsSent = true; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index de7ad83b54..be6d578112 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -5,44 +5,53 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; +import com.google.gwt.core.client.Duration; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONString; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.History; import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.FocusWidget; -import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; -import com.vaadin.terminal.gwt.client.RenderInformation.Size; -import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage; +import com.vaadin.terminal.gwt.client.communication.JsonDecoder; +import com.vaadin.terminal.gwt.client.communication.JsonEncoder; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.RpcManager; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; import com.vaadin.terminal.gwt.client.ui.VContextMenu; -import com.vaadin.terminal.gwt.client.ui.VNotification; -import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent; -import com.vaadin.terminal.gwt.client.ui.VView; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification.HideEvent; +import com.vaadin.terminal.gwt.client.ui.root.RootConnector; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; import com.vaadin.terminal.gwt.server.AbstractCommunicationManager; /** @@ -50,12 +59,10 @@ import com.vaadin.terminal.gwt.server.AbstractCommunicationManager; * communication with its server side counterpart * {@link AbstractCommunicationManager}. * - * Client-side widgets receive updates from the corresponding server-side - * components as calls to - * {@link Paintable#updateFromUIDL(UIDL, ApplicationConnection)} (not to be - * confused with the server side interface {@link com.vaadin.terminal.Paintable} - * ). Any client-side changes (typically resulting from user actions) are sent - * back to the server as variable changes (see {@link #updateVariable()}). + * Client-side connectors receive updates from the corresponding server-side + * connector (typically component) as state updates or RPC calls. The connector + * has the possibility to communicate back with its server side counter part + * through RPC calls. * * TODO document better * @@ -65,27 +72,29 @@ public class ApplicationConnection { // This indicates the whole page is generated by us (not embedded) public static final String GENERATED_BODY_CLASSNAME = "v-generated-body"; - private static final String MODIFIED_CLASSNAME = "v-modified"; + public static final String MODIFIED_CLASSNAME = "v-modified"; public static final String DISABLED_CLASSNAME = "v-disabled"; - private static final String REQUIRED_CLASSNAME_EXT = "-required"; + public static final String REQUIRED_CLASSNAME_EXT = "-required"; - private static final String ERROR_CLASSNAME_EXT = "-error"; + public static final String ERROR_CLASSNAME_EXT = "-error"; - public static final char VAR_RECORD_SEPARATOR = '\u001e'; - - public static final char VAR_FIELD_SEPARATOR = '\u001f'; + public static final String UPDATE_VARIABLE_INTERFACE = "v"; + public static final String UPDATE_VARIABLE_METHOD = "v"; public static final char VAR_BURST_SEPARATOR = '\u001d'; - public static final char VAR_ARRAYITEM_SEPARATOR = '\u001c'; - public static final char VAR_ESCAPE_CHARACTER = '\u001b'; public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key"; /** + * Name of the parameter used to transmit root ids back and forth + */ + public static final String ROOT_ID_PARAMETER = "rootId"; + + /** * @deprecated use UIDL_SECURITY_TOKEN_ID instead */ @Deprecated @@ -93,9 +102,6 @@ public class ApplicationConnection { public static final String PARAM_UNLOADBURST = "onunloadburst"; - public static final String ATTRIBUTE_DESCRIPTION = "description"; - public static final String ATTRIBUTE_ERROR = "error"; - /** * A string that, if found in a non-JSON response to a UIDL request, will * cause the browser to refresh the page. If followed by a colon, optional @@ -117,15 +123,14 @@ public class ApplicationConnection { */ public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh"; + private final boolean debugLogging = false; + // will hold the UIDL security key (for XSS protection) once received private String uidlSecurityKey = "init"; private final HashMap<String, String> resourcesMap = new HashMap<String, String>(); - private final ArrayList<String> pendingVariables = new ArrayList<String>(); - - private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap - .create(); + private ArrayList<MethodInvocation> pendingInvocations = new ArrayList<MethodInvocation>(); private WidgetSet widgetSet; @@ -136,17 +141,19 @@ public class ApplicationConnection { private Timer loadTimer3; private Element loadElement; - private final VView view; + private final RootConnector rootConnector; protected boolean applicationRunning = false; private boolean hasActiveRequest = false; + protected boolean cssLoaded = false; + /** Parameters for this application connection loaded from the web-page */ private ApplicationConfiguration configuration; /** List of pending variable change bursts that must be submitted in order */ - private final ArrayList<ArrayList<String>> pendingVariableBursts = new ArrayList<ArrayList<String>>(); + private final ArrayList<ArrayList<MethodInvocation>> pendingBursts = new ArrayList<ArrayList<MethodInvocation>>(); /** Timer for automatic refirect to SessionExpiredURL */ private Timer redirectTimer; @@ -154,21 +161,42 @@ public class ApplicationConnection { /** redirectTimer scheduling interval in seconds */ private int sessionExpirationInterval; - private ArrayList<Paintable> relativeSizeChanges = new ArrayList<Paintable>();; - private ArrayList<Paintable> componentCaptionSizeChanges = new ArrayList<Paintable>();; + private ArrayList<Widget> componentCaptionSizeChanges = new ArrayList<Widget>(); private Date requestStartTime; private boolean validatingLayouts = false; - private Set<Paintable> zeroWidthComponents = null; + private Set<ComponentConnector> zeroWidthComponents = null; - private Set<Paintable> zeroHeightComponents = null; + private Set<ComponentConnector> zeroHeightComponents = null; - private Set<String> unregistryBag = new HashSet<String>(); + private final LayoutManager layoutManager; + + private final RpcManager rpcManager; + + public static class MultiStepDuration extends Duration { + private int previousStep = elapsedMillis(); + + public void logDuration(String message) { + logDuration(message, 0); + } + + public void logDuration(String message, int minDuration) { + int currentTime = elapsedMillis(); + int stepDuration = currentTime - previousStep; + if (stepDuration >= minDuration) { + VConsole.log(message + ": " + stepDuration + " ms"); + } + previousStep = currentTime; + } + } public ApplicationConnection() { - view = GWT.create(VView.class); + rootConnector = GWT.create(RootConnector.class); + rpcManager = GWT.create(RpcManager.class); + layoutManager = GWT.create(LayoutManager.class); + layoutManager.setConnection(this); } public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) { @@ -186,7 +214,6 @@ public class ApplicationConnection { this.widgetSet = widgetSet; configuration = cnf; - windowName = configuration.getInitialWindowName(); ComponentLocator componentLocator = new ComponentLocator(this); @@ -198,7 +225,7 @@ public class ApplicationConnection { initializeClientHooks(); - view.init(cnf.getRootPanelId(), this); + rootConnector.init(cnf.getRootPanelId(), this); showLoadingIndicator(); } @@ -209,10 +236,19 @@ public class ApplicationConnection { * failed to start. This ensures that the applications are started in order, * to avoid session-id problems. * - * @return */ public void start() { - repaintAll(); + String jsonText = configuration.getUIDL(); + if (jsonText == null) { + // inital UIDL not in DOM, request later + repaintAll(); + } else { + // Update counter so TestBench knows something is still going on + hasActiveRequest = true; + + // initial UIDL provided in DOM, continue as if returned by request + handleJSONText(jsonText, -1); + } } private native void initializeTestbenchHooks( @@ -224,14 +260,13 @@ public class ApplicationConnection { return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()() || ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::isExecutingDeferredCommands()(); }); - var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()(); if (vi) { client.getVersionInfo = function() { return vi; } } - + client.getProfilingData = $entry(function() { var pd = [ ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::lastProcessingTime, @@ -248,10 +283,6 @@ public class ApplicationConnection { return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); }); - if (!$wnd.vaadin.clients) { - $wnd.vaadin.clients = {}; - } - $wnd.vaadin.clients[TTAppId] = client; }-*/; @@ -379,36 +410,31 @@ public class ApplicationConnection { private String getRepaintAllParameters() { // collect some client side data that will be sent to server on // initial uidl request - int clientHeight = Window.getClientHeight(); - int clientWidth = Window.getClientWidth(); - com.google.gwt.dom.client.Element pe = view.getElement() - .getParentElement(); - int offsetHeight = pe.getOffsetHeight(); - int offsetWidth = pe.getOffsetWidth(); - int screenWidth = BrowserInfo.get().getScreenWidth(); - int screenHeight = BrowserInfo.get().getScreenHeight(); - int tzOffset = BrowserInfo.get().getTimezoneOffset(); - int rtzOffset = BrowserInfo.get().getRawTimezoneOffset(); - int dstDiff = BrowserInfo.get().getDSTSavings(); - boolean dstInEffect = BrowserInfo.get().isDSTInEffect(); - long curDate = BrowserInfo.get().getCurrentDate().getTime(); + String nativeBootstrapParameters = getNativeBrowserDetailsParameters(getConfiguration() + .getRootPanelId()); String widgetsetVersion = ApplicationConfiguration.VERSION; - String token = History.getToken(); - // TODO figure out how client and view size could be used better on // server. screen size can be accessed via Browser object, but other // values currently only via transaction listener. - String parameters = "repaintAll=1&" + "sh=" + screenHeight + "&sw=" - + screenWidth + "&cw=" + clientWidth + "&ch=" + clientHeight - + "&vw=" + offsetWidth + "&vh=" + offsetHeight + "&fr=" + token - + "&tzo=" + tzOffset + "&rtzo=" + rtzOffset + "&dstd=" - + dstDiff + "&dston=" + dstInEffect + "&curdate=" + curDate - + "&wsver=" + widgetsetVersion - + (BrowserInfo.get().isTouchDevice() ? "&td=1" : ""); + String parameters = "repaintAll=1&" + nativeBootstrapParameters + + "&wsver=" + widgetsetVersion; return parameters; } + /** + * Gets the browser detail parameters that are sent by the bootstrap + * javascript for two-request initialization. + * + * @param parentElementId + * @return + */ + private static native String getNativeBrowserDetailsParameters( + String parentElementId) + /*-{ + return $wnd.vaadin.getBrowserDetailsParameters(parentElementId); + }-*/; + protected void repaintAll() { String repainAllParameters = getRepaintAllParameters(); makeUidlRequest("", repainAllParameters, false); @@ -427,11 +453,11 @@ public class ApplicationConnection { * Sends a request to the server to print details to console that will help * developer to locate component in the source code. * - * @param paintable + * @param componentConnector */ - void highlightComponent(Paintable paintable) { + void highlightComponent(ComponentConnector componentConnector) { String params = getRepaintAllParameters() + "&highlightComponent=" - + getPid(paintable); + + componentConnector.getConnectorId(); makeUidlRequest("", params, false); } @@ -465,9 +491,8 @@ public class ApplicationConnection { if (extraParams != null && extraParams.length() > 0) { uri = addGetParameters(uri, extraParams); } - if (windowName != null && windowName.length() > 0) { - uri = addGetParameters(uri, "windowName=" + windowName); - } + uri = addGetParameters(uri, + ROOT_ID_PARAMETER + "=" + configuration.getRootId()); doUidlRequest(uri, payload, forceSync); @@ -491,10 +516,6 @@ public class ApplicationConnection { public void onError(Request request, Throwable exception) { showCommunicationError(exception.getMessage(), -1); endRequest(); - if (!applicationRunning) { - // start failed, let's try to start the next app - ApplicationConfiguration.startNextApplication(); - } } public void onResponseReceived(Request request, @@ -578,30 +599,10 @@ public class ApplicationConnection { } } - final Date start = new Date(); // for(;;);[realjson] final String jsonText = response.getText().substring(9, response.getText().length() - 1); - final ValueMap json; - try { - json = parseJSONResponse(jsonText); - } catch (final Exception e) { - endRequest(); - showCommunicationError(e.getMessage() - + " - Original JSON-text:" + jsonText, - statusCode); - return; - } - - VConsole.log("JSON parsing took " - + (new Date().getTime() - start.getTime()) + "ms"); - if (applicationRunning) { - handleReceivedJSONMessage(start, jsonText, json); - } else { - applicationRunning = true; - handleWhenCSSLoaded(jsonText, json); - ApplicationConfiguration.startNextApplication(); - } + handleJSONText(jsonText, statusCode); } }; @@ -626,6 +627,35 @@ public class ApplicationConnection { } /** + * Handles received UIDL JSON text, parsing it, and passing it on to the + * appropriate handlers, while logging timiing information. + * + * @param jsonText + * @param statusCode + */ + private void handleJSONText(String jsonText, int statusCode) { + final Date start = new Date(); + final ValueMap json; + try { + json = parseJSONResponse(jsonText); + } catch (final Exception e) { + endRequest(); + showCommunicationError(e.getMessage() + " - Original JSON-text:" + + jsonText, statusCode); + return; + } + + VConsole.log("JSON parsing took " + + (new Date().getTime() - start.getTime()) + "ms"); + if (applicationRunning) { + handleReceivedJSONMessage(start, jsonText, json); + } else { + applicationRunning = true; + handleWhenCSSLoaded(jsonText, json); + } + } + + /** * Sends an asynchronous UIDL request to the server using the given URI. * * @param uri @@ -674,9 +704,7 @@ public class ApplicationConnection { protected void handleWhenCSSLoaded(final String jsonText, final ValueMap json) { - int heightOfLoadElement = DOM.getElementPropertyInt(loadElement, - "offsetHeight"); - if (heightOfLoadElement == 0 && cssWaits < MAX_CSS_WAITS) { + if (!isCSSLoaded() && cssWaits < MAX_CSS_WAITS) { (new Timer() { @Override public void run() { @@ -688,6 +716,7 @@ public class ApplicationConnection { + "(.v-loading-indicator height == 0)"); cssWaits++; } else { + cssLoaded = true; handleReceivedJSONMessage(new Date(), jsonText, json); if (cssWaits >= MAX_CSS_WAITS) { VConsole.error("CSS files may have not loaded properly."); @@ -696,19 +725,14 @@ public class ApplicationConnection { } /** - * Shows the communication error notification. + * Checks whether or not the CSS is loaded. By default checks the size of + * the loading indicator element. * - * @param details - * Optional details for debugging. - * - * @deprecated Use the method with errorCode instead + * @return */ - @Deprecated - protected void showCommunicationError(String details) { - VConsole.error("Communication error: " + details); - showError(details, configuration.getCommunicationErrorCaption(), - configuration.getCommunicationErrorMessage(), - configuration.getCommunicationErrorUrl()); + protected boolean isCSSLoaded() { + return cssLoaded + || DOM.getElementPropertyInt(loadElement, "offsetHeight") != 0; } /** @@ -717,11 +741,14 @@ public class ApplicationConnection { * @param details * Optional details for debugging. * @param statusCode - * The http error code during the problematic request or -1 if - * error happened before response was received. + * The status code returned for the request + * */ protected void showCommunicationError(String details, int statusCode) { - showCommunicationError(details); + VConsole.error("Communication error: " + details); + ErrorMessage communicationError = configuration.getCommunicationError(); + showError(details, communicationError.getCaption(), + communicationError.getMessage(), communicationError.getUrl()); } /** @@ -732,9 +759,9 @@ public class ApplicationConnection { */ protected void showAuthenticationError(String details) { VConsole.error("Authentication error: " + details); - showError(details, configuration.getAuthorizationErrorCaption(), - configuration.getAuthorizationErrorMessage(), - configuration.getAuthorizationErrorUrl()); + ErrorMessage authorizationError = configuration.getAuthorizationError(); + showError(details, authorizationError.getCaption(), + authorizationError.getMessage(), authorizationError.getUrl()); } /** @@ -831,14 +858,14 @@ public class ApplicationConnection { * change set if it exists. */ private void checkForPendingVariableBursts() { - cleanVariableBurst(pendingVariables); - if (pendingVariableBursts.size() > 0) { - for (Iterator<ArrayList<String>> iterator = pendingVariableBursts + cleanVariableBurst(pendingInvocations); + if (pendingBursts.size() > 0) { + for (Iterator<ArrayList<MethodInvocation>> iterator = pendingBursts .iterator(); iterator.hasNext();) { cleanVariableBurst(iterator.next()); } - ArrayList<String> nextBurst = pendingVariableBursts.get(0); - pendingVariableBursts.remove(0); + ArrayList<MethodInvocation> nextBurst = pendingBursts.get(0); + pendingBursts.remove(0); buildAndSendVariableBurst(nextBurst, false); } } @@ -849,15 +876,13 @@ public class ApplicationConnection { * * @param variableBurst */ - private void cleanVariableBurst(ArrayList<String> variableBurst) { - for (int i = 1; i < variableBurst.size(); i += 2) { - String id = variableBurst.get(i); - id = id.substring(0, id.indexOf(VAR_FIELD_SEPARATOR)); - if (!idToPaintableDetail.containsKey(id) && !id.startsWith("DD")) { + private void cleanVariableBurst(ArrayList<MethodInvocation> variableBurst) { + for (int i = 1; i < variableBurst.size(); i++) { + String id = variableBurst.get(i).getConnectorId(); + if (!getConnectorMap().hasConnector(id) + && !getConnectorMap().isDragAndDropPaintable(id)) { // variable owner does not exist anymore - variableBurst.remove(i - 1); - variableBurst.remove(i - 1); - i -= 2; + variableBurst.remove(i); VConsole.log("Removed variable from removed component: " + id); } } @@ -868,7 +893,7 @@ public class ApplicationConnection { if (loadElement == null) { loadElement = DOM.createDiv(); DOM.setStyleAttribute(loadElement, "position", "absolute"); - DOM.appendChild(view.getElement(), loadElement); + DOM.appendChild(rootConnector.getWidget().getElement(), loadElement); VConsole.log("inserting load indicator"); } DOM.setElementProperty(loadElement, "className", "v-loading-indicator"); @@ -898,12 +923,14 @@ public class ApplicationConnection { private void hideLoadingIndicator() { if (loadTimer != null) { loadTimer.cancel(); - if (loadTimer2 != null) { - loadTimer2.cancel(); - loadTimer3.cancel(); - } loadTimer = null; } + if (loadTimer2 != null) { + loadTimer2.cancel(); + loadTimer3.cancel(); + loadTimer2 = null; + loadTimer3 = null; + } if (loadElement != null) { DOM.setStyleAttribute(loadElement, "display", "none"); } @@ -962,6 +989,7 @@ public class ApplicationConnection { protected void handleUIDLMessage(final Date start, final String jsonText, final ValueMap json) { + VConsole.log("Handling message from server"); // Handle redirect if (json.containsKey("redirect")) { String url = json.getValueMap("redirect").getString("url"); @@ -970,10 +998,13 @@ public class ApplicationConnection { return; } + final MultiStepDuration handleUIDLDuration = new MultiStepDuration(); + // Get security key if (json.containsKey(UIDL_SECURITY_TOKEN_ID)) { uidlSecurityKey = json.getString(UIDL_SECURITY_TOKEN_ID); } + VConsole.log(" * Handling resources from server"); if (json.containsKey("resources")) { ValueMap resources = json.getValueMap("resources"); @@ -984,12 +1015,27 @@ public class ApplicationConnection { resourcesMap.put(key, resources.getAsString(key)); } } + handleUIDLDuration.logDuration( + " * Handling resources from server completed", 10); + + VConsole.log(" * Handling type inheritance map from server"); + + if (json.containsKey("typeInheritanceMap")) { + configuration.addComponentInheritanceInfo(json + .getValueMap("typeInheritanceMap")); + } + handleUIDLDuration.logDuration( + " * Handling type inheritance map from server completed", 10); + + VConsole.log("Handling type mappings from server"); if (json.containsKey("typeMappings")) { configuration.addComponentMappings( json.getValueMap("typeMappings"), widgetSet); } + handleUIDLDuration.logDuration( + " * Handling type mappings from server completed", 10); /* * Hook for TestBench to get details about server status */ @@ -999,27 +1045,42 @@ public class ApplicationConnection { Command c = new Command() { public void execute() { - VConsole.dirUIDL(json, configuration); + handleUIDLDuration.logDuration(" * Loading widgets completed", + 10); + + MultiStepDuration updateDuration = new MultiStepDuration(); + + if (debugLogging) { + VConsole.log(" * Dumping UIDL to the console"); + VConsole.dirUIDL(json, configuration); + + updateDuration.logDuration( + " * Dumping UIDL to the console completed", 10); + } if (json.containsKey("locales")) { + VConsole.log(" * Handling locales"); // Store locale data JsArray<ValueMap> valueMapArray = json .getJSValueMapArray("locales"); LocaleService.addLocales(valueMapArray); } + updateDuration.logDuration(" * Handling locales completed", 10); + boolean repaintAll = false; ValueMap meta = null; if (json.containsKey("meta")) { + VConsole.log(" * Handling meta information"); meta = json.getValueMap("meta"); if (meta.containsKey("repaintAll")) { repaintAll = true; - view.clear(); - idToPaintableDetail.clear(); + rootConnector.getWidget().clear(); + getConnectorMap().clear(); if (meta.containsKey("invalidLayouts")) { validatingLayouts = true; - zeroWidthComponents = new HashSet<Paintable>(); - zeroHeightComponents = new HashSet<Paintable>(); + zeroWidthComponents = new HashSet<ComponentConnector>(); + zeroHeightComponents = new HashSet<ComponentConnector>(); } } if (meta.containsKey("timedRedirect")) { @@ -1036,60 +1097,61 @@ public class ApplicationConnection { } } + updateDuration.logDuration( + " * Handling meta information completed", 10); + if (redirectTimer != null) { redirectTimer.schedule(1000 * sessionExpirationInterval); } - // Process changes - JsArray<ValueMap> changes = json.getJSValueMapArray("changes"); - - ArrayList<Paintable> updatedWidgets = new ArrayList<Paintable>(); - relativeSizeChanges.clear(); componentCaptionSizeChanges.clear(); - int length = changes.length(); - for (int i = 0; i < length; i++) { - try { - final UIDL change = changes.get(i).cast(); - final UIDL uidl = change.getChildUIDL(0); - // TODO optimize - final Paintable paintable = getPaintable(uidl.getId()); - if (paintable != null) { - paintable.updateFromUIDL(uidl, - ApplicationConnection.this); - // paintable may have changed during render to - // another - // implementation, use the new one for updated - // widgets map - updatedWidgets.add(idToPaintableDetail.get( - uidl.getId()).getComponent()); - } else { - if (!uidl.getTag().equals( - configuration.getEncodedWindowTag())) { - VConsole.error("Received update for " - + uidl.getTag() - + ", but there is no such paintable (" - + uidl.getId() + ") rendered."); - } else { - String pid = uidl.getId(); - if (!idToPaintableDetail.containsKey(pid)) { - registerPaintable(pid, view); - } - // VView does not call updateComponent so we - // register any event listeners here - ComponentDetail cd = idToPaintableDetail - .get(pid); - cd.registerEventListenersFromUIDL(uidl); - - // Finally allow VView to update itself - view.updateFromUIDL(uidl, - ApplicationConnection.this); - } - } - } catch (final Throwable e) { - VConsole.error(e); - } - } + int startProcessing = updateDuration.elapsedMillis(); + + // Ensure that all connectors that we are about to update exist + createConnectorsIfNeeded(json); + + updateDuration.logDuration(" * Creating connectors completed", + 10); + + // Update states, do not fire events + Collection<StateChangeEvent> pendingStateChangeEvents = updateConnectorState(json); + + updateDuration.logDuration( + " * Update of connector states completed", 10); + + // Update hierarchy, do not fire events + Collection<ConnectorHierarchyChangeEvent> pendingHierarchyChangeEvents = updateConnectorHierarchy(json); + + updateDuration.logDuration( + " * Update of connector hierarchy completed", 10); + + // Fire hierarchy change events + sendHierarchyChangeEvents(pendingHierarchyChangeEvents); + + updateDuration.logDuration( + " * Hierarchy state change event processing completed", + 10); + + // Fire state change events. + sendStateChangeEvents(pendingStateChangeEvents); + + updateDuration.logDuration( + " * State change event processing completed", 10); + + // Update of legacy (UIDL) style connectors + updateVaadin6StyleConnectors(json); + + updateDuration + .logDuration( + " * Vaadin 6 style connector updates (updateFromUidl) completed", + 10); + + // Handle any RPC invocations done on the server side + handleRpcInvocations(json); + + updateDuration.logDuration( + " * Processing of RPC invocations completed", 10); if (json.containsKey("dd")) { // response contains data for drag and drop service @@ -1097,28 +1159,26 @@ public class ApplicationConnection { json.getValueMap("dd")); } - // Check which widgets' size has been updated - Set<Paintable> sizeUpdatedWidgets = new HashSet<Paintable>(); + updateDuration + .logDuration( + " * Processing of drag and drop server response completed", + 10); - updatedWidgets.addAll(relativeSizeChanges); - sizeUpdatedWidgets.addAll(componentCaptionSizeChanges); + unregisterRemovedConnectors(); - for (Paintable paintable : updatedWidgets) { - ComponentDetail detail = idToPaintableDetail - .get(getPid(paintable)); - Widget widget = (Widget) paintable; - Size oldSize = detail.getOffsetSize(); - Size newSize = new Size(widget.getOffsetWidth(), - widget.getOffsetHeight()); + updateDuration.logDuration( + " * Unregistering of removed components completed", 10); - if (oldSize == null || !oldSize.equals(newSize)) { - sizeUpdatedWidgets.add(paintable); - detail.setOffsetSize(newSize); - } + VConsole.log("handleUIDLMessage: " + + (updateDuration.elapsedMillis() - startProcessing) + + " ms"); - } + LayoutManager layoutManager = getLayoutManager(); + layoutManager.setEverythingNeedsMeasure(); + layoutManager.layoutNow(); - Util.componentSizeUpdated(sizeUpdatedWidgets); + updateDuration + .logDuration(" * Layout processing completed", 10); if (meta != null) { if (meta.containsKey("appError")) { @@ -1162,15 +1222,7 @@ public class ApplicationConnection { } } - if (repaintAll) { - /* - * idToPaintableDetail is already cleanded at the start of - * the changeset handling, bypass cleanup. - */ - unregistryBag.clear(); - } else { - purgeUnregistryBag(); - } + updateDuration.logDuration(" * Error handling completed", 10); // TODO build profiling for widget impl loading time @@ -1181,213 +1233,383 @@ public class ApplicationConnection { VConsole.log(" Processing time was " + String.valueOf(lastProcessingTime) + "ms for " + jsonText.length() + " characters of JSON"); - VConsole.log("Referenced paintables: " - + idToPaintableDetail.size()); + VConsole.log("Referenced paintables: " + connectorMap.size()); endRequest(); } - }; - ApplicationConfiguration.runWhenWidgetsLoaded(c); - } - /** - * This method assures that all pending variable changes are sent to server. - * Method uses synchronized xmlhttprequest and does not return before the - * changes are sent. No UIDL updates are processed and thus UI is left in - * inconsistent state. This method should be called only when closing - * windows - normally sendPendingVariableChanges() should be used. - */ - public void sendPendingVariableChangesSync() { - if (applicationRunning) { - pendingVariableBursts.add(pendingVariables); - ArrayList<String> nextBurst = pendingVariableBursts.get(0); - pendingVariableBursts.remove(0); - buildAndSendVariableBurst(nextBurst, true); - } - } + /** + * Sends the state change events created while updating the state + * information. + * + * This must be called after hierarchy change listeners have been + * called. At least caption updates for the parent are strange if + * fired from state change listeners and thus calls the parent + * BEFORE the parent is aware of the child (through a + * ConnectorHierarchyChangedEvent) + * + * @param pendingStateChangeEvents + * The events to send + */ + private void sendStateChangeEvents( + Collection<StateChangeEvent> pendingStateChangeEvents) { + VConsole.log(" * Sending state change events"); - // Redirect browser, null reloads current page - private static native void redirect(String url) - /*-{ - if (url) { - $wnd.location = url; - } else { - $wnd.location.reload(false); - } - }-*/; + for (StateChangeEvent sce : pendingStateChangeEvents) { + try { + sce.getConnector().fireEvent(sce); + } catch (final Throwable e) { + VConsole.error(e); + } + } - public void registerPaintable(String pid, Paintable paintable) { - ComponentDetail componentDetail = new ComponentDetail(this, pid, - paintable); - idToPaintableDetail.put(pid, componentDetail); - setPid(((Widget) paintable).getElement(), pid); - } + } - private native void setPid(Element el, String pid) - /*-{ - el.tkPid = pid; - }-*/; + private void unregisterRemovedConnectors() { + int unregistered = 0; + List<ServerConnector> currentConnectors = new ArrayList<ServerConnector>( + connectorMap.getConnectors()); + for (ServerConnector c : currentConnectors) { + if (c instanceof ComponentConnector) { + ComponentConnector cc = (ComponentConnector) c; + if (cc.getParent() != null) { + if (!cc.getParent().getChildren().contains(cc)) { + VConsole.error("ERROR: Connector is connected to a parent but the parent does not contain the connector"); + } + } else if ((cc instanceof RootConnector && cc == getRootConnector())) { + // RootConnector for this connection, leave as-is + } else if (cc instanceof WindowConnector + && getRootConnector().hasSubWindow( + (WindowConnector) cc)) { + // Sub window attached to this RootConnector, leave + // as-is + } else { + // The connector has been detached from the + // hierarchy, unregister it and any possible + // children. The RootConnector should never be + // unregistered even though it has no parent. + connectorMap.unregisterConnector(cc); + unregistered++; + } + } - /** - * Gets the paintableId for a specific paintable (a.k.a Vaadin Widget). - * <p> - * The paintableId is used in the UIDL to identify a specific widget - * instance, effectively linking the widget with it's server side Component. - * </p> - * - * @param paintable - * the paintable who's id is needed - * @return the id for the given paintable - */ - public String getPid(Paintable paintable) { - return getPid(((Widget) paintable).getElement()); - } + } - /** - * Gets the paintableId using a DOM element - the element should be the main - * element for a paintable otherwise no id will be found. Use - * {@link #getPid(Paintable)} instead whenever possible. - * - * @see #getPid(Paintable) - * @param el - * element of the paintable whose pid is desired - * @return the pid of the element's paintable, if it's a paintable - */ - public native String getPid(Element el) - /*-{ - return el.tkPid; - }-*/; + VConsole.log("* Unregistered " + unregistered + " connectors"); + } - /** - * Gets the main element for the paintable with the given id. The revers of - * {@link #getPid(Element)}. - * - * @param pid - * the pid of the widget whose element is desired - * @return the element for the paintable corresponding to the pid - */ - public Element getElementByPid(String pid) { - return ((Widget) getPaintable(pid)).getElement(); - } + private void createConnectorsIfNeeded(ValueMap json) { + VConsole.log(" * Creating connectors (if needed)"); - /** - * Unregisters the given paintable; always use after removing a paintable. - * This method does not remove the paintable from the DOM, but marks the - * paintable so that ApplicationConnection may clean up its references to - * it. Removing the widget from DOM is component containers responsibility. - * - * @param p - * the paintable to remove - */ - public void unregisterPaintable(Paintable p) { + if (!json.containsKey("types")) { + return; + } - // add to unregistry que + ValueMap types = json.getValueMap("types"); + JsArrayString keyArray = types.getKeyArray(); + for (int i = 0; i < keyArray.length(); i++) { + try { + String connectorId = keyArray.get(i); + int connectorType = Integer.parseInt(types + .getString((connectorId))); + ServerConnector connector = connectorMap + .getConnector(connectorId); + if (connector != null) { + continue; + } - if (p == null) { - VConsole.error("WARN: Trying to unregister null paintable"); - return; - } - String id = getPid(p); - if (id == null) { - /* - * Uncomment the following to debug unregistring components. No - * paintables with null id should end here. At least one exception - * is our VScrollTableRow, that is hacked to fake it self as a - * Paintable to build support for sizing easier. - */ - // if (!(p instanceof VScrollTableRow)) { - // VConsole.log("Trying to unregister Paintable not created by Application Connection."); - // } - if (p instanceof HasWidgets) { - unregisterChildPaintables((HasWidgets) p); + Class<? extends ComponentConnector> connectorClass = configuration + .getWidgetClassByEncodedTag(connectorType); + + // Connector does not exist so we must create it + if (connectorClass != RootConnector.class) { + // create, initialize and register the paintable + getConnector(connectorId, connectorType); + } else { + // First RootConnector update. Before this the + // RootConnector has been created but not + // initialized as the connector id has not been + // known + connectorMap.registerConnector(connectorId, + rootConnector); + rootConnector.doInit(connectorId, + ApplicationConnection.this); + } + } catch (final Throwable e) { + VConsole.error(e); + } + } } - } else { - unregistryBag.add(id); - if (p instanceof HasWidgets) { - unregisterChildPaintables((HasWidgets) p); + + private void updateVaadin6StyleConnectors(ValueMap json) { + JsArray<ValueMap> changes = json.getJSValueMapArray("changes"); + int length = changes.length(); + + VConsole.log(" * Passing UIDL to Vaadin 6 style connectors"); + // update paintables + for (int i = 0; i < length; i++) { + try { + final UIDL change = changes.get(i).cast(); + final UIDL uidl = change.getChildUIDL(0); + String connectorId = uidl.getId(); + + final ComponentConnector legacyConnector = (ComponentConnector) connectorMap + .getConnector(connectorId); + if (legacyConnector instanceof Paintable) { + ((Paintable) legacyConnector).updateFromUIDL(uidl, + ApplicationConnection.this); + } else if (legacyConnector == null) { + VConsole.error("Received update for " + + uidl.getTag() + + ", but there is no such paintable (" + + connectorId + ") rendered."); + } else { + VConsole.error("Server sent Vaadin 6 style updates for " + + Util.getConnectorString(legacyConnector) + + " but this is not a Vaadin 6 Paintable"); + } + + } catch (final Throwable e) { + VConsole.error(e); + } + } } - } - } - private void purgeUnregistryBag() { - for (String id : unregistryBag) { - ComponentDetail componentDetail = idToPaintableDetail.get(id); - if (componentDetail == null) { - /* - * this should never happen, but it does :-( See e.g. - * com.vaadin.tests.components.accordion.RemoveTabs (with test - * script) - */ - VConsole.error("ApplicationConnetion tried to unregister component (id=" - + id - + ") that is never registered (or already unregistered)"); - continue; + private void sendHierarchyChangeEvents( + Collection<ConnectorHierarchyChangeEvent> pendingHierarchyChangeEvents) { + if (pendingHierarchyChangeEvents.isEmpty()) { + return; + } + + VConsole.log(" * Sending hierarchy change events"); + for (ConnectorHierarchyChangeEvent event : pendingHierarchyChangeEvents) { + try { + event.getConnector().fireEvent(event); + } catch (final Throwable e) { + VConsole.error(e); + } + } + } - // check if can be cleaned - Widget component = (Widget) componentDetail.getComponent(); - if (!component.isAttached()) { - // clean reference from ac to paintable - idToPaintableDetail.remove(id); + + private Collection<StateChangeEvent> updateConnectorState( + ValueMap json) { + ArrayList<StateChangeEvent> events = new ArrayList<StateChangeEvent>(); + VConsole.log(" * Updating connector states"); + if (!json.containsKey("state")) { + return events; + } + // set states for all paintables mentioned in "state" + ValueMap states = json.getValueMap("state"); + JsArrayString keyArray = states.getKeyArray(); + for (int i = 0; i < keyArray.length(); i++) { + try { + String connectorId = keyArray.get(i); + ServerConnector connector = connectorMap + .getConnector(connectorId); + if (null != connector) { + + JSONArray stateDataAndType = new JSONArray( + states.getJavaScriptObject(connectorId)); + + Object state = JsonDecoder.decodeValue( + stateDataAndType, connectorMap, + ApplicationConnection.this); + + connector.setState((SharedState) state); + StateChangeEvent event = GWT + .create(StateChangeEvent.class); + event.setConnector(connector); + events.add(event); + } + } catch (final Throwable e) { + VConsole.error(e); + } + } + + return events; } - /* - * else NOP : same component has been reattached to another parent - * or replaced by another component implementation. + + /** + * Updates the connector hierarchy and returns a list of events that + * should be fired after update of the hierarchy and the state is + * done. + * + * @param json + * The JSON containing the hierarchy information + * @return A collection of events that should be fired when update + * of hierarchy and state is complete */ - } + private Collection<ConnectorHierarchyChangeEvent> updateConnectorHierarchy( + ValueMap json) { + List<ConnectorHierarchyChangeEvent> events = new LinkedList<ConnectorHierarchyChangeEvent>(); - unregistryBag.clear(); - } + VConsole.log(" * Updating connector hierarchy"); + if (!json.containsKey("hierarchy")) { + return events; + } + + ValueMap hierarchies = json.getValueMap("hierarchy"); + JsArrayString hierarchyKeys = hierarchies.getKeyArray(); + for (int i = 0; i < hierarchyKeys.length(); i++) { + try { + String connectorId = hierarchyKeys.get(i); + ServerConnector connector = connectorMap + .getConnector(connectorId); + if (!(connector instanceof ComponentContainerConnector)) { + VConsole.error("Retrieved a hierarchy update for a connector (" + + connectorId + + ") that is not a ComponentContainerConnector"); + continue; + } + ComponentContainerConnector ccc = (ComponentContainerConnector) connector; + + JsArrayString childConnectorIds = hierarchies + .getJSStringArray(connectorId); + int childConnectorSize = childConnectorIds.length(); + + List<ServerConnector> newChildren = new ArrayList<ServerConnector>(); + for (int connectorIndex = 0; connectorIndex < childConnectorSize; connectorIndex++) { + String childConnectorId = childConnectorIds + .get(connectorIndex); + ComponentConnector childConnector = (ComponentConnector) connectorMap + .getConnector(childConnectorId); + if (childConnector == null) { + VConsole.error("Hierarchy claims that " + + childConnectorId + " is a child for " + + connectorId + " (" + + connector.getClass().getName() + + ") but no connector with id " + + childConnectorId + + " has been registered"); + continue; + } + newChildren.add(childConnector); + if (childConnector.getParent() != ccc) { + // Avoid extra calls to setParent + childConnector.setParent(ccc); + } + } + + // TODO This check should be done on the server side in + // the future so the hierarchy update is only sent when + // something actually has changed + List<ComponentConnector> oldChildren = ccc + .getChildren(); + boolean actuallyChanged = !Util.collectionsEquals( + oldChildren, newChildren); + + if (!actuallyChanged) { + continue; + } + + // Fire change event if the hierarchy has changed + ConnectorHierarchyChangeEvent event = GWT + .create(ConnectorHierarchyChangeEvent.class); + event.setOldChildren(oldChildren); + event.setConnector(ccc); + ccc.setChildren((List) newChildren); + events.add(event); + + // Remove parent for children that are no longer + // attached to this (avoid updating children if they + // have already been assigned to a new parent) + for (ComponentConnector oldChild : oldChildren) { + if (oldChild.getParent() != ccc) { + continue; + } + + // TODO This could probably be optimized + if (!newChildren.contains(oldChild)) { + oldChild.setParent(null); + } + } + } catch (final Throwable e) { + VConsole.error(e); + } + } + return events; - /** - * Unregisters a paintable and all it's child paintables recursively. Use - * when after removing a paintable that contains other paintables. Does not - * unregister the given container itself. Does not actually remove the - * paintable from the DOM. - * - * @see #unregisterPaintable(Paintable) - * @param container - */ - public void unregisterChildPaintables(HasWidgets container) { - final Iterator<Widget> it = container.iterator(); - while (it.hasNext()) { - final Widget w = it.next(); - if (w instanceof Paintable) { - unregisterPaintable((Paintable) w); - } else if (w instanceof HasWidgets) { - unregisterChildPaintables((HasWidgets) w); } - } + + private void handleRpcInvocations(ValueMap json) { + if (json.containsKey("rpc")) { + VConsole.log(" * Performing server to client RPC calls"); + + JSONArray rpcCalls = new JSONArray( + json.getJavaScriptObject("rpc")); + + int rpcLength = rpcCalls.size(); + for (int i = 0; i < rpcLength; i++) { + try { + JSONArray rpcCall = (JSONArray) rpcCalls.get(i); + MethodInvocation invocation = parseMethodInvocation(rpcCall); + VConsole.log("Server to client RPC call: " + + invocation); + rpcManager.applyInvocation(invocation, + getConnectorMap()); + } catch (final Throwable e) { + VConsole.error(e); + } + } + } + + } + + }; + ApplicationConfiguration.runWhenWidgetsLoaded(c); } - /** - * Returns Paintable element by its id - * - * @param id - * Paintable ID - */ - public Paintable getPaintable(String id) { - ComponentDetail componentDetail = idToPaintableDetail.get(id); - if (componentDetail == null) { - return null; - } else { - return componentDetail.getComponent(); + private MethodInvocation parseMethodInvocation(JSONArray rpcCall) { + String connectorId = ((JSONString) rpcCall.get(0)).stringValue(); + String interfaceName = ((JSONString) rpcCall.get(1)).stringValue(); + String methodName = ((JSONString) rpcCall.get(2)).stringValue(); + JSONArray parametersJson = (JSONArray) rpcCall.get(3); + Object[] parameters = new Object[parametersJson.size()]; + for (int j = 0; j < parametersJson.size(); ++j) { + parameters[j] = JsonDecoder.decodeValue( + (JSONArray) parametersJson.get(j), getConnectorMap(), this); } + return new MethodInvocation(connectorId, interfaceName, methodName, + parameters); } - private void addVariableToQueue(String paintableId, String variableName, - String encodedValue, boolean immediate, char type) { - final String id = paintableId + VAR_FIELD_SEPARATOR + variableName - + VAR_FIELD_SEPARATOR + type; - for (int i = 1; i < pendingVariables.size(); i += 2) { - if ((pendingVariables.get(i)).equals(id)) { - pendingVariables.remove(i - 1); - pendingVariables.remove(i - 1); - break; - } - } - pendingVariables.add(encodedValue); - pendingVariables.add(id); + // Redirect browser, null reloads current page + private static native void redirect(String url) + /*-{ + if (url) { + $wnd.location = url; + } else { + $wnd.location.reload(false); + } + }-*/; + + private void addVariableToQueue(String connectorId, String variableName, + Object value, boolean immediate) { + // note that type is now deduced from value + // TODO could eliminate invocations of same shared variable setter + addMethodInvocationToQueue(new MethodInvocation(connectorId, + UPDATE_VARIABLE_INTERFACE, UPDATE_VARIABLE_METHOD, + new Object[] { variableName, value }), immediate); + } + + /** + * Adds an explicit RPC method invocation to the send queue. + * + * @since 7.0 + * + * @param invocation + * RPC method invocation + * @param immediate + * true to trigger sending within a short time window (possibly + * combining subsequent calls to a single request), false to let + * the framework delay sending of RPC calls and variable changes + * until the next immediate change + */ + public void addMethodInvocationToQueue(MethodInvocation invocation, + boolean immediate) { + pendingInvocations.add(invocation); if (immediate) { sendPendingVariableChanges(); } @@ -1403,20 +1625,32 @@ public class ApplicationConnection { * "burst" to queue that will be purged after current request is handled. * */ - @SuppressWarnings("unchecked") public void sendPendingVariableChanges() { + if (!deferedSendPending) { + deferedSendPending = true; + Scheduler.get().scheduleDeferred(sendPendingCommand); + } + } + + private final ScheduledCommand sendPendingCommand = new ScheduledCommand() { + public void execute() { + deferedSendPending = false; + doSendPendingVariableChanges(); + } + }; + private boolean deferedSendPending = false; + + @SuppressWarnings("unchecked") + private void doSendPendingVariableChanges() { if (applicationRunning) { if (hasActiveRequest()) { // skip empty queues if there are pending bursts to be sent - if (pendingVariables.size() > 0 - || pendingVariableBursts.size() == 0) { - ArrayList<String> burst = (ArrayList<String>) pendingVariables - .clone(); - pendingVariableBursts.add(burst); - pendingVariables.clear(); + if (pendingInvocations.size() > 0 || pendingBursts.size() == 0) { + pendingBursts.add(pendingInvocations); + pendingInvocations = new ArrayList<MethodInvocation>(); } } else { - buildAndSendVariableBurst(pendingVariables, false); + buildAndSendVariableBurst(pendingInvocations, false); } } } @@ -1428,39 +1662,64 @@ public class ApplicationConnection { * at the same time. This is ok as we can assume that DOM will never be * updated after this. * - * @param pendingVariables - * Vector of variable changes to send + * @param pendingInvocations + * List of RPC method invocations to send * @param forceSync * Should we use synchronous request? */ - private void buildAndSendVariableBurst(ArrayList<String> pendingVariables, - boolean forceSync) { + private void buildAndSendVariableBurst( + ArrayList<MethodInvocation> pendingInvocations, boolean forceSync) { final StringBuffer req = new StringBuffer(); - while (!pendingVariables.isEmpty()) { + while (!pendingInvocations.isEmpty()) { if (ApplicationConfiguration.isDebugMode()) { - Util.logVariableBurst(this, pendingVariables); + Util.logVariableBurst(this, pendingInvocations); } - for (int i = 0; i < pendingVariables.size(); i++) { - if (i > 0) { - if (i % 2 == 0) { - req.append(VAR_RECORD_SEPARATOR); - } else { - req.append(VAR_FIELD_SEPARATOR); - } + + JSONArray reqJson = new JSONArray(); + + for (MethodInvocation invocation : pendingInvocations) { + JSONArray invocationJson = new JSONArray(); + invocationJson.set(0, + new JSONString(invocation.getConnectorId())); + invocationJson.set(1, + new JSONString(invocation.getInterfaceName())); + invocationJson.set(2, + new JSONString(invocation.getMethodName())); + JSONArray paramJson = new JSONArray(); + for (int i = 0; i < invocation.getParameters().length; ++i) { + // TODO non-static encoder? type registration? + paramJson.set(i, JsonEncoder.encode( + invocation.getParameters()[i], getConnectorMap(), + this)); } - req.append(pendingVariables.get(i)); + invocationJson.set(3, paramJson); + reqJson.set(reqJson.size(), invocationJson); } - pendingVariables.clear(); - // Append all the busts to this synchronous request - if (forceSync && !pendingVariableBursts.isEmpty()) { - pendingVariables = pendingVariableBursts.get(0); - pendingVariableBursts.remove(0); + // escape burst separators (if any) + req.append(escapeBurstContents(reqJson.toString())); + + pendingInvocations.clear(); + // Append all the bursts to this synchronous request + if (forceSync && !pendingBursts.isEmpty()) { + pendingInvocations = pendingBursts.get(0); + pendingBursts.remove(0); req.append(VAR_BURST_SEPARATOR); } } - makeUidlRequest(req.toString(), "", forceSync); + + // Include the browser detail parameters if they aren't already sent + String extraParams; + if (!getConfiguration().isBrowserDetailsSent()) { + extraParams = getNativeBrowserDetailsParameters(getConfiguration() + .getRootPanelId()); + getConfiguration().setBrowserDetailsSent(); + } else { + extraParams = ""; + } + + makeUidlRequest(req.toString(), extraParams, forceSync); } /** @@ -1481,9 +1740,8 @@ public class ApplicationConnection { * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, - Paintable newValue, boolean immediate) { - String pid = (newValue != null) ? getPid(newValue) : null; - addVariableToQueue(paintableId, variableName, pid, immediate, 'p'); + ServerConnector newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); } /** @@ -1506,8 +1764,7 @@ public class ApplicationConnection { public void updateVariable(String paintableId, String variableName, String newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, - escapeVariableValue(newValue), immediate, 's'); + addVariableToQueue(paintableId, variableName, newValue, immediate); } /** @@ -1530,8 +1787,7 @@ public class ApplicationConnection { public void updateVariable(String paintableId, String variableName, int newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'i'); + addVariableToQueue(paintableId, variableName, newValue, immediate); } /** @@ -1554,8 +1810,7 @@ public class ApplicationConnection { public void updateVariable(String paintableId, String variableName, long newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'l'); + addVariableToQueue(paintableId, variableName, newValue, immediate); } /** @@ -1578,8 +1833,7 @@ public class ApplicationConnection { public void updateVariable(String paintableId, String variableName, float newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'f'); + addVariableToQueue(paintableId, variableName, newValue, immediate); } /** @@ -1602,8 +1856,7 @@ public class ApplicationConnection { public void updateVariable(String paintableId, String variableName, double newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'd'); + addVariableToQueue(paintableId, variableName, newValue, immediate); } /** @@ -1626,8 +1879,7 @@ public class ApplicationConnection { public void updateVariable(String paintableId, String variableName, boolean newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue ? "true" - : "false", immediate, 'b'); + addVariableToQueue(paintableId, variableName, newValue, immediate); } /** @@ -1642,56 +1894,14 @@ public class ApplicationConnection { * the id of the paintable that owns the variable * @param variableName * the name of the variable - * @param newValue - * the new value to be sent + * @param map + * the new values to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, Map<String, Object> map, boolean immediate) { - final StringBuffer buf = new StringBuffer(); - Iterator<String> iterator = map.keySet().iterator(); - while (iterator.hasNext()) { - String key = iterator.next(); - Object value = map.get(key); - char transportType = getTransportType(value); - buf.append(transportType); - buf.append(escapeVariableValue(key)); - buf.append(VAR_ARRAYITEM_SEPARATOR); - if (transportType == 'p') { - buf.append(getPid((Paintable) value)); - } else { - buf.append(escapeVariableValue(String.valueOf(value))); - } - - if (iterator.hasNext()) { - buf.append(VAR_ARRAYITEM_SEPARATOR); - } - } - - addVariableToQueue(paintableId, variableName, buf.toString(), - immediate, 'm'); - } - - private char getTransportType(Object value) { - if (value instanceof String) { - return 's'; - } else if (value instanceof Paintable) { - return 'p'; - } else if (value instanceof Boolean) { - return 'b'; - } else if (value instanceof Integer) { - return 'i'; - } else if (value instanceof Float) { - return 'f'; - } else if (value instanceof Double) { - return 'd'; - } else if (value instanceof Long) { - return 'l'; - } else if (value instanceof Enum) { - return 's'; // transported as string representation - } - return 'u'; + addVariableToQueue(paintableId, variableName, map, immediate); } /** @@ -1707,25 +1917,14 @@ public class ApplicationConnection { * the id of the paintable that owns the variable * @param variableName * the name of the variable - * @param newValue + * @param values * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, String[] values, boolean immediate) { - final StringBuffer buf = new StringBuffer(); - if (values != null) { - for (int i = 0; i < values.length; i++) { - buf.append(escapeVariableValue(values[i])); - // there will be an extra separator at the end to differentiate - // between an empty array and one containing an empty string - // only - buf.append(VAR_ARRAYITEM_SEPARATOR); - } - } - addVariableToQueue(paintableId, variableName, buf.toString(), - immediate, 'c'); + addVariableToQueue(paintableId, variableName, values, immediate); } /** @@ -1742,44 +1941,25 @@ public class ApplicationConnection { * the id of the paintable that owns the variable * @param variableName * the name of the variable - * @param newValue + * @param values * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, Object[] values, boolean immediate) { - final StringBuffer buf = new StringBuffer(); - if (values != null) { - for (int i = 0; i < values.length; i++) { - if (i > 0) { - buf.append(VAR_ARRAYITEM_SEPARATOR); - } - Object value = values[i]; - char transportType = getTransportType(value); - // first char tells the type in array - buf.append(transportType); - if (transportType == 'p') { - buf.append(getPid((Paintable) value)); - } else { - buf.append(escapeVariableValue(String.valueOf(value))); - } - } - } - addVariableToQueue(paintableId, variableName, buf.toString(), - immediate, 'a'); + addVariableToQueue(paintableId, variableName, values, immediate); } /** - * Encode burst, record, field and array item separator characters in a - * String for transport over the network. This protects from separator - * injection attacks. + * Encode burst separator characters in a String for transport over the + * network. This protects from separator injection attacks. * * @param value * to encode * @return encoded value */ - protected String escapeVariableValue(String value) { + protected String escapeBurstContents(String value) { final StringBuilder result = new StringBuilder(); for (int i = 0; i < value.length(); ++i) { char character = value.charAt(i); @@ -1787,9 +1967,6 @@ public class ApplicationConnection { case VAR_ESCAPE_CHARACTER: // fall-through - escape character is duplicated case VAR_BURST_SEPARATOR: - case VAR_RECORD_SEPARATOR: - case VAR_FIELD_SEPARATOR: - case VAR_ARRAYITEM_SEPARATOR: result.append(VAR_ESCAPE_CHARACTER); // encode as letters for easier reading result.append(((char) (character + 0x30))); @@ -1803,288 +1980,6 @@ public class ApplicationConnection { return result.toString(); } - /** - * Update generic component features. - * - * <h2>Selecting correct implementation</h2> - * - * <p> - * The implementation of a component depends on many properties, including - * styles, component features, etc. Sometimes the user changes those - * properties after the component has been created. Calling this method in - * the beginning of your updateFromUIDL -method automatically replaces your - * component with more appropriate if the requested implementation changes. - * </p> - * - * <h2>Caption, icon, error messages and description</h2> - * - * <p> - * Component can delegate management of caption, icon, error messages and - * description to parent layout. This is optional an should be decided by - * component author - * </p> - * - * <h2>Component visibility and disabling</h2> - * - * This method will manage component visibility automatically and if - * component is an instanceof FocusWidget, also handle component disabling - * when needed. - * - * @param component - * Widget to be updated, expected to implement an instance of - * Paintable - * @param uidl - * UIDL to be painted - * @param manageCaption - * True if you want to delegate caption, icon, description and - * error message management to parent. - * - * @return Returns true iff no further painting is needed by caller - */ - public boolean updateComponent(Widget component, UIDL uidl, - boolean manageCaption) { - String pid = getPid(component.getElement()); - if (pid == null) { - VConsole.error("Trying to update an unregistered component: " - + Util.getSimpleName(component)); - return true; - } - - ComponentDetail componentDetail = idToPaintableDetail.get(pid); - - if (componentDetail == null) { - VConsole.error("ComponentDetail not found for " - + Util.getSimpleName(component) + " with PID " + pid - + ". This should not happen."); - return true; - } - - // If the server request that a cached instance should be used, do - // nothing - if (uidl.getBooleanAttribute("cached")) { - return true; - } - - // register the listened events by the server-side to the event-handler - // of the component - componentDetail.registerEventListenersFromUIDL(uidl); - - // Visibility - boolean visible = !uidl.getBooleanAttribute("invisible"); - boolean wasVisible = component.isVisible(); - component.setVisible(visible); - if (wasVisible != visible) { - // Changed invisibile <-> visible - if (wasVisible && manageCaption) { - // Must hide caption when component is hidden - final Container parent = Util.getLayout(component); - if (parent != null) { - parent.updateCaption((Paintable) component, uidl); - } - - } - } - - String id = uidl.getId(); - if (configuration.useDebugIdInDOM() && id.startsWith("PID_")) { - // PIDs with a debug id have form "PID_[seq]S[debugid]" where [seq] - // is either empty or a sequential integer used to uniquify - // different Paintables having the same debug id. See #5109. - DOM.setElementProperty(component.getElement(), "id", - id.substring(id.indexOf('S') + 1)); - } - - if (!visible) { - // component is invisible, delete old size to notify parent, if - // later make visible - componentDetail.setOffsetSize(null); - return true; - } - - // Switch to correct implementation if needed - if (!widgetSet.isCorrectImplementation(component, uidl, configuration)) { - final Widget w = (Widget) widgetSet.createWidget(uidl, - configuration); - // deferred binding check TODO change isCorrectImplementation to use - // stored detected class, making this innecessary - if (w.getClass() != component.getClass()) { - final Container parent = Util.getLayout(component); - if (parent != null) { - parent.replaceChildComponent(component, w); - unregisterPaintable((Paintable) component); - registerPaintable(uidl.getId(), (Paintable) w); - ((Paintable) w).updateFromUIDL(uidl, this); - return true; - } - } - } - - boolean enabled = !uidl.getBooleanAttribute("disabled"); - if (uidl.hasAttribute("tabindex") && component instanceof Focusable) { - ((Focusable) component).setTabIndex(uidl - .getIntAttribute("tabindex")); - } - /* - * Disabled state may affect (override) tabindex so the order must be - * first setting tabindex, then enabled state. - */ - if (component instanceof FocusWidget) { - FocusWidget fw = (FocusWidget) component; - fw.setEnabled(enabled); - } - - TooltipInfo tooltipInfo = componentDetail.getTooltipInfo(null); - // Update tooltip - if (uidl.hasAttribute(ATTRIBUTE_DESCRIPTION)) { - tooltipInfo - .setTitle(uidl.getStringAttribute(ATTRIBUTE_DESCRIPTION)); - } else { - tooltipInfo.setTitle(null); - } - - // add error classname to components w/ error - if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { - tooltipInfo.setErrorUidl(uidl.getErrors()); - } else { - tooltipInfo.setErrorUidl(null); - } - - // Style names - component.setStyleName(getStyleName(component.getStylePrimaryName(), - uidl, component instanceof Field)); - - // Set captions - if (manageCaption) { - final Container parent = Util.getLayout(component); - if (parent != null) { - parent.updateCaption((Paintable) component, uidl); - } - } - /* - * updateComponentSize need to be after caption update so caption can be - * taken into account - */ - - updateComponentSize(componentDetail, uidl); - - return false; - } - - /** - * Generates the style name for the widget based on the given primary style - * name (typically returned by Widget.getPrimaryStyleName()) and the UIDL. - * An additional "modified" style name can be added if the field parameter - * is set to true. - * - * @param primaryStyleName - * @param uidl - * @param isField - * @return - */ - public static String getStyleName(String primaryStyleName, UIDL uidl, - boolean field) { - boolean enabled = !uidl.getBooleanAttribute("disabled"); - - StringBuffer styleBuf = new StringBuffer(); - styleBuf.append(primaryStyleName); - - // first disabling and read-only status - if (!enabled) { - styleBuf.append(" "); - styleBuf.append(DISABLED_CLASSNAME); - } - if (uidl.getBooleanAttribute("readonly")) { - styleBuf.append(" "); - styleBuf.append("v-readonly"); - } - - // add additional styles as css classes, prefixed with component default - // stylename - if (uidl.hasAttribute("style")) { - final String[] styles = uidl.getStringAttribute("style").split(" "); - for (int i = 0; i < styles.length; i++) { - styleBuf.append(" "); - styleBuf.append(primaryStyleName); - styleBuf.append("-"); - styleBuf.append(styles[i]); - styleBuf.append(" "); - styleBuf.append(styles[i]); - } - } - - // add modified classname to Fields - if (field && uidl.hasAttribute("modified")) { - styleBuf.append(" "); - styleBuf.append(MODIFIED_CLASSNAME); - } - - if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { - styleBuf.append(" "); - styleBuf.append(primaryStyleName); - styleBuf.append(ERROR_CLASSNAME_EXT); - } - // add required style to required components - if (uidl.hasAttribute("required")) { - styleBuf.append(" "); - styleBuf.append(primaryStyleName); - styleBuf.append(REQUIRED_CLASSNAME_EXT); - } - - return styleBuf.toString(); - - } - - private void updateComponentSize(ComponentDetail cd, UIDL uidl) { - String w = uidl.hasAttribute("width") ? uidl - .getStringAttribute("width") : ""; - - String h = uidl.hasAttribute("height") ? uidl - .getStringAttribute("height") : ""; - - float relativeWidth = Util.parseRelativeSize(w); - float relativeHeight = Util.parseRelativeSize(h); - - // First update maps so they are correct in the setHeight/setWidth calls - if (relativeHeight >= 0.0 || relativeWidth >= 0.0) { - // One or both is relative - FloatSize relativeSize = new FloatSize(relativeWidth, - relativeHeight); - if (cd.getRelativeSize() == null && cd.getOffsetSize() != null) { - // The component has changed from absolute size to relative size - relativeSizeChanges.add(cd.getComponent()); - } - cd.setRelativeSize(relativeSize); - } else if (relativeHeight < 0.0 && relativeWidth < 0.0) { - if (cd.getRelativeSize() != null) { - // The component has changed from relative size to absolute size - relativeSizeChanges.add(cd.getComponent()); - } - cd.setRelativeSize(null); - } - - Widget component = (Widget) cd.getComponent(); - // Set absolute sizes - if (relativeHeight < 0.0) { - component.setHeight(h); - } - if (relativeWidth < 0.0) { - component.setWidth(w); - } - - // Set relative sizes - if (relativeHeight >= 0.0 || relativeWidth >= 0.0) { - // One or both is relative - handleComponentRelativeSize(cd); - } - - } - - /** - * Traverses recursively child widgets until ContainerResizedListener child - * widget is found. They will delegate it further if needed. - * - * @param container - */ private boolean runningLayout = false; /** @@ -2106,11 +2001,11 @@ public class ApplicationConnection { * development. Published to JavaScript. */ public void forceLayout() { - Set<Paintable> set = new HashSet<Paintable>(); - for (ComponentDetail cd : idToPaintableDetail.values()) { - set.add(cd.getComponent()); - } - Util.componentSizeUpdated(set); + Duration duration = new Duration(); + + layoutManager.forceLayout(); + + VConsole.log("forceLayout in " + duration.elapsedMillis() + " ms"); } private void internalRunDescendentsLayout(HasWidgets container) { @@ -2120,7 +2015,7 @@ public class ApplicationConnection { while (childWidgets.hasNext()) { final Widget child = childWidgets.next(); - if (child instanceof Paintable) { + if (getConnectorMap().isConnector(child)) { if (handleComponentRelativeSize(child)) { /* @@ -2151,131 +2046,8 @@ public class ApplicationConnection { * @param child * @return true if the child has a relative size */ - private boolean handleComponentRelativeSize(ComponentDetail cd) { - if (cd == null) { - return false; - } - boolean debugSizes = false; - - FloatSize relativeSize = cd.getRelativeSize(); - if (relativeSize == null) { - return false; - } - Widget widget = (Widget) cd.getComponent(); - - boolean horizontalScrollBar = false; - boolean verticalScrollBar = false; - - Container parent = Util.getLayout(widget); - RenderSpace renderSpace; - - // Parent-less components (like sub-windows) are relative to browser - // window. - if (parent == null) { - renderSpace = new RenderSpace(Window.getClientWidth(), - Window.getClientHeight()); - } else { - renderSpace = parent.getAllocatedSpace(widget); - } - - if (relativeSize.getHeight() >= 0) { - if (renderSpace != null) { - - if (renderSpace.getScrollbarSize() > 0) { - if (relativeSize.getWidth() > 100) { - horizontalScrollBar = true; - } else if (relativeSize.getWidth() < 0 - && renderSpace.getWidth() > 0) { - int offsetWidth = widget.getOffsetWidth(); - int width = renderSpace.getWidth(); - if (offsetWidth > width) { - horizontalScrollBar = true; - } - } - } - - int height = renderSpace.getHeight(); - if (horizontalScrollBar) { - height -= renderSpace.getScrollbarSize(); - } - if (validatingLayouts && height <= 0) { - zeroHeightComponents.add(cd.getComponent()); - } - - height = (int) (height * relativeSize.getHeight() / 100.0); - - if (height < 0) { - height = 0; - } - - if (debugSizes) { - VConsole.log("Widget " + Util.getSimpleName(widget) + "/" - + getPid(widget.getElement()) + " relative height " - + relativeSize.getHeight() + "% of " - + renderSpace.getHeight() + "px (reported by " - - + Util.getSimpleName(parent) + "/" - + (parent == null ? "?" : parent.hashCode()) - + ") : " + height + "px"); - } - widget.setHeight(height + "px"); - } else { - widget.setHeight(relativeSize.getHeight() + "%"); - VConsole.error(Util.getLayout(widget).getClass().getName() - + " did not produce allocatedSpace for " - + widget.getClass().getName()); - } - } - - if (relativeSize.getWidth() >= 0) { - - if (renderSpace != null) { - - int width = renderSpace.getWidth(); - - if (renderSpace.getScrollbarSize() > 0) { - if (relativeSize.getHeight() > 100) { - verticalScrollBar = true; - } else if (relativeSize.getHeight() < 0 - && renderSpace.getHeight() > 0 - && widget.getOffsetHeight() > renderSpace - .getHeight()) { - verticalScrollBar = true; - } - } - - if (verticalScrollBar) { - width -= renderSpace.getScrollbarSize(); - } - if (validatingLayouts && width <= 0) { - zeroWidthComponents.add(cd.getComponent()); - } - - width = (int) (width * relativeSize.getWidth() / 100.0); - - if (width < 0) { - width = 0; - } - - if (debugSizes) { - VConsole.log("Widget " + Util.getSimpleName(widget) + "/" - + getPid(widget.getElement()) + " relative width " - + relativeSize.getWidth() + "% of " - + renderSpace.getWidth() + "px (reported by " - + Util.getSimpleName(parent) + "/" - + (parent == null ? "?" : getPid(parent)) + ") : " - + width + "px"); - } - widget.setWidth(width + "px"); - } else { - widget.setWidth(relativeSize.getWidth() + "%"); - VConsole.error(Util.getLayout(widget).getClass().getName() - + " did not produce allocatedSpace for " - + widget.getClass().getName()); - } - } - - return true; + private boolean handleComponentRelativeSize(ComponentConnector paintable) { + return false; } /** @@ -2284,57 +2056,61 @@ public class ApplicationConnection { * @param child * @return true if the child has a relative size */ - public boolean handleComponentRelativeSize(Widget child) { - return handleComponentRelativeSize(idToPaintableDetail.get(getPid(child - .getElement()))); + public boolean handleComponentRelativeSize(Widget widget) { + return handleComponentRelativeSize(connectorMap.getConnector(widget)); } - /** - * Gets the specified Paintables relative size (percent). - * - * @param widget - * the paintable whose size is needed - * @return the the size if the paintable is relatively sized, -1 otherwise - */ - public FloatSize getRelativeSize(Widget widget) { - return idToPaintableDetail.get(getPid(widget.getElement())) - .getRelativeSize(); + @Deprecated + public ComponentConnector getPaintable(UIDL uidl) { + return getConnector(uidl.getId(), Integer.parseInt(uidl.getTag())); } /** - * Get either existing or new Paintable for given UIDL. + * Get either an existing ComponentConnector or create a new + * ComponentConnector with the given type and id. * - * If corresponding Paintable has been previously painted, return it. - * Otherwise create and register a new Paintable from UIDL. Caller must - * update the returned Paintable from UIDL after it has been connected to - * parent. + * If a ComponentConnector with the given id already exists, returns it. + * Otherwise creates and registers a new ComponentConnector of the given + * type. * - * @param uidl - * UIDL to create Paintable from. - * @return Either existing or new Paintable corresponding to UIDL. - */ - public Paintable getPaintable(UIDL uidl) { - final String id = uidl.getId(); - Paintable w = getPaintable(id); - if (w != null) { - return w; - } else { - w = widgetSet.createWidget(uidl, configuration); - registerPaintable(id, w); - return w; - + * @param connectorId + * Id of the paintable + * @param connectorType + * Type of the connector, as passed from the server side + * + * @return Either an existing ComponentConnector or a new ComponentConnector + * of the given type + */ + public ComponentConnector getConnector(String connectorId, int connectorType) { + if (!connectorMap.hasConnector(connectorId)) { + return createAndRegisterConnector(connectorId, connectorType); } + return (ComponentConnector) connectorMap.getConnector(connectorId); } /** - * Returns a Paintable element by its root element + * Creates a new ComponentConnector with the given type and id. + * + * Creates and registers a new ComponentConnector of the given type. Should + * never be called with the connector id of an existing connector. + * + * @param connectorId + * Id of the new connector + * @param connectorType + * Type of the connector, as passed from the server side * - * @param element - * Root element of the paintable + * @return A new ComponentConnector of the given type */ - public Paintable getPaintable(Element element) { - return getPaintable(getPid(element)); + private ComponentConnector createAndRegisterConnector(String connectorId, + int connectorType) { + // Create and register a new connector with the given type + ComponentConnector p = widgetSet.createWidget(connectorType, + configuration); + connectorMap.registerConnector(connectorId, p); + p.doInit(connectorId, this); + + return p; } /** @@ -2427,16 +2203,12 @@ public class ApplicationConnection { * Updating TooltipInfo is done in updateComponent method. * */ - public TooltipInfo getTooltipTitleInfo(Paintable titleOwner, Object key) { + public TooltipInfo getTooltipTitleInfo(ComponentConnector titleOwner, + Object key) { if (null == titleOwner) { return null; } - ComponentDetail cd = idToPaintableDetail.get(getPid(titleOwner)); - if (null != cd) { - return cd.getTooltipInfo(key); - } else { - return null; - } + return connectorMap.getTooltipInfo(titleOwner, key); } private final VTooltip tooltip = new VTooltip(this); @@ -2451,7 +2223,7 @@ public class ApplicationConnection { * @param event * @param owner */ - public void handleTooltipEvent(Event event, Paintable owner) { + public void handleTooltipEvent(Event event, ComponentConnector owner) { tooltip.handleTooltipEvent(event, owner, null); } @@ -2469,73 +2241,13 @@ public class ApplicationConnection { * the key for tooltip if this is "additional" tooltip, null for * components "main tooltip" */ - public void handleTooltipEvent(Event event, Paintable owner, Object key) { + public void handleTooltipEvent(Event event, ComponentConnector owner, + Object key) { tooltip.handleTooltipEvent(event, owner, key); } - /** - * Adds PNG-fix conditionally (only for IE6) to the specified IMG -element. - * - * @param el - * the IMG element to fix - */ - public void addPngFix(Element el) { - BrowserInfo b = BrowserInfo.get(); - if (b.isIE6()) { - Util.addPngFix(el); - } - } - - /* - * Helper to run layout functions triggered by child components with a - * decent interval. - */ - private final Timer layoutTimer = new Timer() { - - private boolean isPending = false; - - @Override - public void schedule(int delayMillis) { - if (!isPending) { - super.schedule(delayMillis); - isPending = true; - } - } - - @Override - public void run() { - VConsole.log("Running re-layout of " + view.getClass().getName()); - runDescendentsLayout(view); - isPending = false; - } - }; - - /** - * Components can call this function to run all layout functions. This is - * usually done, when component knows that its size has changed. - */ - public void requestLayoutPhase() { - layoutTimer.schedule(500); - } - - private String windowName = null; - - /** - * Reset the name of the current browser-window. This should reflect the - * window-name used in the server, but might be different from the - * window-object target-name on client. - * - * @param stringAttribute - * New name for the window. - */ - public void setWindowName(String newName) { - windowName = newName; - } - - protected String getWindowName() { - return windowName; - } + private ConnectorMap connectorMap = GWT.create(ConnectorMap.class); protected String getUidlSecurityKey() { return uidlSecurityKey; @@ -2548,17 +2260,17 @@ public class ApplicationConnection { * @param component * the Paintable whose caption has changed */ - public void captionSizeUpdated(Paintable component) { - componentCaptionSizeChanges.add(component); + public void captionSizeUpdated(Widget widget) { + componentCaptionSizeChanges.add(widget); } /** - * Gets the main view, a.k.a top-level window. + * Gets the main view * * @return the main view */ - public VView getView() { - return view; + public RootConnector getRootConnector() { + return rootConnector; } /** @@ -2567,7 +2279,7 @@ public class ApplicationConnection { * this method. * <p> * Component must also pipe events to - * {@link #handleTooltipEvent(Event, Paintable, Object)} method. + * {@link #handleTooltipEvent(Event, ComponentConnector, Object)} method. * <p> * This method can also be used to deregister tooltips by using null as * tooltip @@ -2577,17 +2289,16 @@ public class ApplicationConnection { * @param key * key assosiated with given tooltip. Can be any object. For * example a related dom element. Same key must be given for - * {@link #handleTooltipEvent(Event, Paintable, Object)} method. + * {@link #handleTooltipEvent(Event, ComponentConnector, Object)} + * method. * * @param tooltip * the TooltipInfo object containing details shown in tooltip, * null if deregistering tooltip */ - public void registerTooltip(Paintable paintable, Object key, + public void registerTooltip(ComponentConnector paintable, Object key, TooltipInfo tooltip) { - ComponentDetail componentDetail = idToPaintableDetail - .get(getPid(paintable)); - componentDetail.putAdditionalTooltip(key, tooltip); + connectorMap.registerTooltip(paintable, key, tooltip); } /** @@ -2606,14 +2317,18 @@ public class ApplicationConnection { * before the component is updated so the value is correct if called from * updatedFromUIDL. * + * @param paintable + * The connector to register event listeners for * @param eventIdentifier * The identifier for the event * @return true if at least one listener has been registered on server side * for the event identified by eventIdentifier. + * @deprecated Use {@link ComponentState#hasEventListener(String)} instead */ - public boolean hasEventListeners(Paintable paintable, String eventIdentifier) { - return idToPaintableDetail.get(getPid(paintable)).hasEventListeners( - eventIdentifier); + @Deprecated + public boolean hasEventListeners(ComponentConnector paintable, + String eventIdentifier) { + return paintable.hasEventListener(eventIdentifier); } /** @@ -2657,4 +2372,71 @@ public class ApplicationConnection { return uri; } + ConnectorMap getConnectorMap() { + return connectorMap; + } + + @Deprecated + public void unregisterPaintable(ServerConnector p) { + System.out.println("unregisterPaintable (unnecessarily) called for " + + Util.getConnectorString(p)); + // connectorMap.unregisterConnector(p); + } + + public VTooltip getVTooltip() { + return tooltip; + } + + @Deprecated + public void handleTooltipEvent(Event event, Widget owner, Object key) { + handleTooltipEvent(event, getConnectorMap().getConnector(owner), key); + } + + /** + * Method provided for backwards compatibility. Duties previously done by + * this method is now handled by the state change event handler in + * AbstractComponentConnector. The only function this method has is to + * return true if the UIDL is a "cached" update. + * + * @param component + * @param uidl + * @param manageCaption + * @return + */ + @Deprecated + public boolean updateComponent(Widget component, UIDL uidl, + boolean manageCaption) { + ComponentConnector connector = getConnectorMap() + .getConnector(component); + if (!AbstractComponentConnector.isRealUpdate(uidl)) { + return true; + } + + if (!manageCaption) { + VConsole.error(Util.getConnectorString(connector) + + " called updateComponent with manageCaption=false. The parameter was ignored - override delegateCaption() to return false instead. It is however not recommended to use caption this way at all."); + } + return false; + } + + @Deprecated + public void handleTooltipEvent(Event event, Widget owner) { + handleTooltipEvent(event, getConnectorMap().getConnector(owner)); + + } + + @Deprecated + public void registerTooltip(Widget owner, Object key, TooltipInfo info) { + registerTooltip(getConnectorMap().getConnector(owner), key, info); + } + + @Deprecated + public boolean hasEventListeners(Widget widget, String eventIdentifier) { + return hasEventListeners(getConnectorMap().getConnector(widget), + eventIdentifier); + } + + LayoutManager getLayoutManager() { + return layoutManager; + } } diff --git a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java index 844b4f2e96..ef1dc481b1 100644 --- a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java +++ b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java @@ -4,8 +4,6 @@ package com.vaadin.terminal.gwt.client; -import java.util.Date; - import com.google.gwt.user.client.ui.RootPanel; /** @@ -66,11 +64,11 @@ public class BrowserInfo { browserDetails.setIEMode(documentMode); } } - + if (browserDetails.isChrome()) { - touchDevice = detectChromeTouchDevice(); + touchDevice = detectChromeTouchDevice(); } else { - touchDevice = detectTouchDevice(); + touchDevice = detectTouchDevice(); } } @@ -78,7 +76,7 @@ public class BrowserInfo { /*-{ try { document.createEvent("TouchEvent");return true;} catch(e){return false;}; }-*/; - + private native boolean detectChromeTouchDevice() /*-{ return ("ontouchstart" in window); @@ -202,14 +200,6 @@ public class BrowserInfo { return isSafari() && browserDetails.getBrowserMajorVersion() == 4; } - public boolean isIE6() { - return isIE() && browserDetails.getBrowserMajorVersion() == 6; - } - - public boolean isIE7() { - return isIE() && browserDetails.getBrowserMajorVersion() == 7; - } - public boolean isIE8() { return isIE() && browserDetails.getBrowserMajorVersion() == 8; } @@ -230,23 +220,6 @@ public class BrowserInfo { return browserDetails.isWebKit(); } - public boolean isFF2() { - // FIXME: Should use browserVersion - return browserDetails.isFirefox() - && browserDetails.getBrowserEngineVersion() == 1.8; - } - - public boolean isFF3() { - // FIXME: Should use browserVersion - return browserDetails.isFirefox() - && browserDetails.getBrowserEngineVersion() == 1.9; - } - - public boolean isFF4() { - return browserDetails.isFirefox() - && browserDetails.getBrowserMajorVersion() == 4; - } - /** * Returns the Gecko version if the browser is Gecko based. The Gecko * version for Firefox 2 is 1.8 and 1.9 for Firefox 3. @@ -321,92 +294,22 @@ public class BrowserInfo { }-*/; /** - * Get's the timezone offset from GMT in minutes, as reported by the - * browser. DST affects this value. - * - * @return offset to GMT in minutes - */ - public native int getTimezoneOffset() - /*-{ - return new Date().getTimezoneOffset(); - }-*/; - - /** - * Gets the timezone offset from GMT in minutes, as reported by the browser - * AND adjusted to ignore daylight savings time. DST does not affect this - * value. - * - * @return offset to GMT in minutes - */ - public native int getRawTimezoneOffset() - /*-{ - var d = new Date(); - var tzo1 = d.getTimezoneOffset(); // current offset - - for (var m=12;m>0;m--) { - d.setUTCMonth(m); - var tzo2 = d.getTimezoneOffset(); - if (tzo1 != tzo2) { - // NOTE js indicates this 'backwards' (e.g -180) - return (tzo1 > tzo2 ? tzo1 : tzo2); // offset w/o DST - } - } - - return tzo1; // no DST - - }-*/; - - /** - * Gets the difference in minutes between the browser's GMT timezone and - * DST. - * - * @return the amount of minutes that the timezone shifts when DST is in - * effect - */ - public native int getDSTSavings() - /*-{ - var d = new Date(); - var tzo1 = d.getTimezoneOffset(); // current offset - - for (var m=12;m>0;m--) { - d.setUTCMonth(m); - var tzo2 = d.getTimezoneOffset(); - if (tzo1 != tzo2) { - // NOTE js indicates this 'backwards' (e.g -180) - return (tzo1 > tzo2 ? tzo1-tzo2 : tzo2-tzo1); // offset w/o DST - } - } - - return 0; // no DST - }-*/; - - /** - * Determines whether daylight savings time (DST) is currently in effect in - * the region of the browser or not. - * - * @return true if the browser resides at a location that currently is in - * DST + * @return true if the browser runs on a touch based device. */ - public boolean isDSTInEffect() { - return getTimezoneOffset() != getRawTimezoneOffset(); + public boolean isTouchDevice() { + return touchDevice; } /** - * Returns the current date and time of the browser. This will not be - * entirely accurate due to varying network latencies, but should provide a - * close-enough value for most cases. + * Indicates whether the browser might require juggling to properly update + * sizes inside elements with overflow: auto. * - * @return the current date and time of the browser. - */ - public Date getCurrentDate() { - return new Date(); - } - - /** - * @return true if the browser runs on a touch based device. + * @return <code>true</code> if the browser requires the workaround, + * otherwise <code>false</code> */ - public boolean isTouchDevice() { - return touchDevice; + public boolean requiresOverflowAutoFix() { + return (getWebkitVersion() > 0 || getOperaVersion() >= 11) + && Util.getNativeScrollbarSize() > 0; } } diff --git a/src/com/vaadin/terminal/gwt/client/ComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java new file mode 100644 index 0000000000..5f9171084e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java @@ -0,0 +1,130 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import com.google.gwt.user.client.ui.Widget; + +/** + * An interface used by client-side widgets or paintable parts to receive + * updates from the corresponding server-side components in the form of + * {@link UIDL}. + * + * Updates can be sent back to the server using the + * {@link ApplicationConnection#updateVariable()} methods. + */ +public interface ComponentConnector extends ServerConnector { + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.VPaintable#getState() + */ + public ComponentState getState(); + + /** + * Returns the widget for this {@link ComponentConnector} + */ + public Widget getWidget(); + + public LayoutManager getLayoutManager(); + + /** + * Returns <code>true</code> if the width of this paintable is currently + * undefined. If the width is undefined, the actual width of the paintable + * is defined by its contents. + * + * @return <code>true</code> if the width is undefined, else + * <code>false</code> + */ + public boolean isUndefinedWidth(); + + /** + * Returns <code>true</code> if the height of this paintable is currently + * undefined. If the height is undefined, the actual height of the paintable + * is defined by its contents. + * + * @return <code>true</code> if the height is undefined, else + * <code>false</code> + */ + public boolean isUndefinedHeight(); + + /** + * Returns <code>true</code> if the width of this paintable is currently + * relative. If the width is relative, the actual width of the paintable is + * a percentage of the size allocated to it by its parent. + * + * @return <code>true</code> if the width is undefined, else + * <code>false</code> + */ + public boolean isRelativeWidth(); + + /** + * Returns <code>true</code> if the height of this paintable is currently + * relative. If the height is relative, the actual height of the paintable + * is a percentage of the size allocated to it by its parent. + * + * @return <code>true</code> if the width is undefined, else + * <code>false</code> + */ + public boolean isRelativeHeight(); + + /** + * Returns the parent of this connector. Can be null for only the root + * connector. + * + * @return The parent of this connector, as set by + * {@link #setParent(ComponentContainerConnector)}. + */ + public ComponentContainerConnector getParent(); + + /** + * Sets the parent for this connector. This method should only be called by + * the framework to ensure that the connector hierarchy on the client side + * and the server side are in sync. + * <p> + * Note that calling this method does not fire a + * {@link ConnectorHierarchyChangeEvent}. The event is fired only when the + * whole hierarchy has been updated. + * + * @param parent + * The new parent of the connector + */ + public void setParent(ComponentContainerConnector parent); + + /** + * Checks if the connector is read only. + * + * @deprecated This belongs in AbstractFieldConnector, see #8514 + * @return true + */ + @Deprecated + public boolean isReadOnly(); + + public boolean hasEventListener(String eventIdentifier); + + /** + * Return true if parent handles caption, false if the paintable handles the + * caption itself. + * + * <p> + * This should always return true and all components should let the parent + * handle the caption and use other attributes for internal texts in the + * component + * </p> + * + * @return true if caption handling is delegated to the parent, false if + * parent should not be allowed to render caption + */ + public boolean delegateCaptionHandling(); + + /** + * Sets the enabled state of the widget associated to this connector. + * + * @param widgetEnabled + * true if the widget should be enabled, false otherwise + */ + public void setWidgetEnabled(boolean widgetEnabled); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java b/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java new file mode 100644 index 0000000000..05334e8049 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java @@ -0,0 +1,74 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.List; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.HasWidgets; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; + +/** + * An interface used by client-side connectors whose widget is a component + * container (implements {@link HasWidgets}). + */ +public interface ComponentContainerConnector extends ComponentConnector { + + /** + * Update child components caption, description and error message. + * + * <p> + * Each component is responsible for maintaining its caption, description + * and error message. In most cases components doesn't want to do that and + * those elements reside outside of the component. Because of this layouts + * must provide service for it's childen to show those elements for them. + * </p> + * + * @param connector + * Child component for which service is requested. + */ + void updateCaption(ComponentConnector connector); + + /** + * Returns the children for this connector. + * <p> + * The children for this connector are defined as all + * {@link ComponentConnector}s whose parent is this + * {@link ComponentContainerConnector}. + * </p> + * + * @return A collection of children for this connector. An empty collection + * if there are no children. Never returns null. + */ + public List<ComponentConnector> getChildren(); + + /** + * Sets the children for this connector. This method should only be called + * by the framework to ensure that the connector hierarchy on the client + * side and the server side are in sync. + * <p> + * Note that calling this method does not call + * {@link #connectorHierarchyChanged(ConnectorHierarchyChangeEvent)}. The + * event method is called only when the hierarchy has been updated for all + * connectors. + * + * @param children + * The new child connectors + */ + public void setChildren(List<ComponentConnector> children); + + /** + * Adds a handler that is called whenever the child hierarchy of this + * connector has been updated by the server. + * + * @param handler + * The handler that should be added. + * @return A handler registration reference that can be used to unregister + * the handler + */ + public HandlerRegistration addConnectorHierarchyChangeHandler( + ConnectorHierarchyChangeHandler handler); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ComponentDetail.java b/src/com/vaadin/terminal/gwt/client/ComponentDetail.java index 7fc93b2a3e..686cb640a4 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentDetail.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentDetail.java @@ -5,20 +5,12 @@ package com.vaadin.terminal.gwt.client; import java.util.HashMap; -import com.google.gwt.core.client.JsArrayString; -import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; -import com.vaadin.terminal.gwt.client.RenderInformation.Size; - class ComponentDetail { - private Paintable component; private TooltipInfo tooltipInfo = new TooltipInfo(); - private String pid; - public ComponentDetail(ApplicationConnection client, String pid, - Paintable component) { - this.component = component; - this.pid = pid; + public ComponentDetail() { + } /** @@ -48,54 +40,8 @@ class ComponentDetail { this.tooltipInfo = tooltipInfo; } - private FloatSize relativeSize; - private Size offsetSize; private HashMap<Object, TooltipInfo> additionalTooltips; - /** - * @return the pid - */ - String getPid() { - return pid; - } - - /** - * @return the component - */ - Paintable getComponent() { - return component; - } - - /** - * @return the relativeSize - */ - FloatSize getRelativeSize() { - return relativeSize; - } - - /** - * @param relativeSize - * the relativeSize to set - */ - void setRelativeSize(FloatSize relativeSize) { - this.relativeSize = relativeSize; - } - - /** - * @return the offsetSize - */ - Size getOffsetSize() { - return offsetSize; - } - - /** - * @param offsetSize - * the offsetSize to set - */ - void setOffsetSize(Size offsetSize) { - this.offsetSize = offsetSize; - } - public void putAdditionalTooltip(Object key, TooltipInfo tooltip) { if (tooltip == null && additionalTooltips != null) { additionalTooltips.remove(key); @@ -107,38 +53,4 @@ class ComponentDetail { } } - private JsArrayString eventListeners; - - /** - * Stores the event listeners registered on server-side and passed along in - * the UIDL. - * - * @param componentUIDL - * The UIDL for the component - * @since 6.2 - */ - native void registerEventListenersFromUIDL(UIDL uidl) - /*-{ - this.@com.vaadin.terminal.gwt.client.ComponentDetail::eventListeners = uidl[1].eventListeners; - }-*/; - - /** - * Checks if there is a registered server side listener for the event. - * - * @param eventIdentifier - * The identifier for the event - * @return true if at least one listener has been registered on server side - * for the event identified by eventIdentifier. - */ - public boolean hasEventListeners(String eventIdentifier) { - if (eventListeners != null) { - int l = eventListeners.length(); - for (int i = 0; i < l; i++) { - if (eventListeners.get(i).equals(eventIdentifier)) { - return true; - } - } - } - return false; - } } diff --git a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java index b71b5fdeb5..d847d49e6f 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; @@ -12,8 +13,12 @@ import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.SubPartAware; -import com.vaadin.terminal.gwt.client.ui.VView; -import com.vaadin.terminal.gwt.client.ui.VWindow; +import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.VMeasuringOrderedLayout; +import com.vaadin.terminal.gwt.client.ui.root.VRoot; +import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheetPanel; +import com.vaadin.terminal.gwt.client.ui.window.VWindow; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; /** * ComponentLocator provides methods for generating a String locator for a given @@ -78,7 +83,7 @@ public class ComponentLocator { Element e = targetElement; while (true) { - pid = client.getPid(e); + pid = ConnectorMap.get(client).getConnectorId(e); if (pid != null) { break; } @@ -94,7 +99,8 @@ public class ComponentLocator { // If we found a Paintable then we use that as reference. We should // find the Paintable for all but very special cases (like // overlays). - w = (Widget) client.getPaintable(pid); + w = ((ComponentConnector) ConnectorMap.get(client) + .getConnector(pid)).getWidget(); /* * Still if the Paintable contains a widget that implements @@ -364,18 +370,14 @@ public class ComponentLocator { return null; } - String pid = client.getPid(w.getElement()); - if (isStaticPid(pid)) { - return pid; - } - - if (w instanceof VView) { + if (w instanceof VRoot) { return ""; } else if (w instanceof VWindow) { - VWindow win = (VWindow) w; - ArrayList<VWindow> subWindowList = client.getView() - .getSubWindowList(); - int indexOfSubWindow = subWindowList.indexOf(win); + Connector windowConnector = ConnectorMap.get(client) + .getConnector(w); + List<WindowConnector> subWindowList = client.getRootConnector() + .getSubWindows(); + int indexOfSubWindow = subWindowList.indexOf(windowConnector); return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]"; } else if (w instanceof RootPanel) { return ROOT_ID; @@ -434,10 +436,25 @@ public class ComponentLocator { if (part.equals(ROOT_ID)) { w = RootPanel.get(); } else if (part.equals("")) { - w = client.getView(); + w = client.getRootConnector().getWidget(); } else if (w == null) { - // Must be static pid (PID_*) - w = (Widget) client.getPaintable(part); + String id = part; + // Must be old static pid (PID_S*) + ComponentConnector connector = (ComponentConnector) ConnectorMap + .get(client).getConnector(id); + if (connector == null) { + // Lookup by debugId + // TODO Optimize this + connector = findConnectorById(client.getRootConnector(), + id.substring(5)); + } + + if (connector != null) { + w = connector.getWidget(); + } else { + // Not found + return null; + } } else if (part.startsWith("domChild[")) { // The target widget has been found and the rest identifies the // element @@ -455,6 +472,67 @@ public class ComponentLocator { int widgetPosition = Integer.parseInt(indexString.substring(0, indexString.length() - 1)); + // AbsolutePanel in GridLayout has been removed -> skip it + if (w instanceof VGridLayout + && "AbsolutePanel".equals(widgetClassName)) { + continue; + } + + if (w instanceof VTabsheetPanel && widgetPosition != 0) { + // TabSheetPanel now only contains 1 connector => the index + // is always 0 which indicates the widget in the active tab + widgetPosition = 0; + } + + /* + * The new grid and ordered layotus do not contain + * ChildComponentContainer widgets. This is instead simulated by + * constructing a path step that would find the desired widget + * from the layout and injecting it as the next search step + * (which would originally have found the widget inside the + * ChildComponentContainer) + */ + if ((w instanceof VMeasuringOrderedLayout || w instanceof VGridLayout) + && "ChildComponentContainer".equals(widgetClassName) + && i + 1 < parts.length) { + + HasWidgets layout = (HasWidgets) w; + + String nextPart = parts[i + 1]; + String[] nextSplit = nextPart.split("\\[", 2); + String nextWidgetClassName = nextSplit[0]; + + // Find the n:th child and count the number of children with + // the same type before it + int nextIndex = 0; + for (Widget child : layout) { + boolean matchingType = nextWidgetClassName.equals(Util + .getSimpleName(child)); + if (matchingType && widgetPosition == 0) { + // This is the n:th child that we looked for + break; + } else if (widgetPosition < 0) { + // Error if we're past the desired position without + // a match + return null; + } else if (matchingType) { + // If this was another child of the expected type, + // increase the count for the next step + nextIndex++; + } + + // Don't count captions + if (!(child instanceof VCaption)) { + widgetPosition--; + } + } + + // Advance to the next step, this time checking for the + // actual child widget + parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']'; + continue; + } + // Locate the child Iterator<? extends Widget> iterator; @@ -463,7 +541,14 @@ public class ComponentLocator { * compatibility */ if (widgetClassName.equals("VWindow")) { - iterator = client.getView().getSubWindowList().iterator(); + List<WindowConnector> windows = client.getRootConnector() + .getSubWindows(); + List<VWindow> windowWidgets = new ArrayList<VWindow>( + windows.size()); + for (WindowConnector wc : windows) { + windowWidgets.add(wc.getWidget()); + } + iterator = windowWidgets.iterator(); } else if (widgetClassName.equals("VContextMenu")) { return client.getContextMenu(); } else { @@ -503,19 +588,23 @@ public class ComponentLocator { return w; } - /** - * Checks if the given pid is a static pid. - * - * @param pid - * The pid to check - * @return true if the pid is a static pid, false otherwise - */ - private boolean isStaticPid(String pid) { - if (pid == null) { - return false; + private ComponentConnector findConnectorById(ComponentConnector root, + String id) { + if (root instanceof ComponentConnector + && id.equals(root.getState().getDebugId())) { + return root; + } + if (root instanceof ComponentContainerConnector) { + ComponentContainerConnector ccc = (ComponentContainerConnector) root; + for (ComponentConnector child : ccc.getChildren()) { + ComponentConnector found = findConnectorById(child, id); + if (found != null) { + return found; + } + } } - return pid.startsWith("PID_"); + return null; } } diff --git a/src/com/vaadin/terminal/gwt/client/ComponentState.java b/src/com/vaadin/terminal/gwt/client/ComponentState.java new file mode 100644 index 0000000000..10cd14251b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ComponentState.java @@ -0,0 +1,406 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.client.communication.URLReference; +import com.vaadin.ui.Component; + +/** + * Default shared state implementation for UI components. + * + * State classes of concrete components should extend this class. + * + * @since 7.0 + */ +public class ComponentState extends SharedState { + private String height = ""; + private String width = ""; + private boolean readOnly = false; + private boolean immediate = false; + private boolean enabled = true; + private String description = ""; + // Note: for the caption, there is a difference between null and an empty + // string! + private String caption = null; + private boolean visible = true; + private URLReference icon = null; + private List<String> styles = null; + private String debugId = null; + /** + * A set of event identifiers with registered listeners. + */ + private Set<String> registeredEventListeners = null; + + // HTML formatted error message for the component + // TODO this could be an object with more information, but currently the UI + // only uses the message + private String errorMessage = null; + + /** + * Returns the component height as set by the server. + * + * Can be relative (containing the percent sign) or absolute, or empty + * string for undefined height. + * + * @return component height as defined by the server, not null + */ + public String getHeight() { + if (height == null) { + return ""; + } + return height; + } + + /** + * Sets the height of the component in the server format. + * + * Can be relative (containing the percent sign) or absolute, or null or + * empty string for undefined height. + * + * @param height + * component height + */ + public void setHeight(String height) { + this.height = height; + } + + /** + * Returns true if the component height is undefined, false if defined + * (absolute or relative). + * + * @return true if component height is undefined + */ + public boolean isUndefinedHeight() { + return "".equals(getHeight()); + } + + /** + * Returns the component width as set by the server. + * + * Can be relative (containing the percent sign) or absolute, or empty + * string for undefined height. + * + * @return component width as defined by the server, not null + */ + public String getWidth() { + if (width == null) { + return ""; + } + return width; + } + + /** + * Sets the width of the component in the server format. + * + * Can be relative (containing the percent sign) or absolute, or null or + * empty string for undefined width. + * + * @param width + * component width + */ + public void setWidth(String width) { + this.width = width; + } + + /** + * Returns true if the component width is undefined, false if defined + * (absolute or relative). + * + * @return true if component width is undefined + */ + public boolean isUndefinedWidth() { + return "".equals(getWidth()); + } + + /** + * Returns true if the component is in read-only mode. + * + * @see com.vaadin.ui.Component#isReadOnly() + * + * @return true if the component is in read-only mode + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Sets or resets the read-only mode for a component. + * + * @see com.vaadin.ui.Component#setReadOnly() + * + * @param readOnly + * new mode for the component + */ + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + /** + * Returns true if the component is in immediate mode. + * + * @see com.vaadin.terminal.VariableOwner#isImmediate() + * + * @return true if the component is in immediate mode + */ + public boolean isImmediate() { + return immediate; + } + + /** + * Sets or resets the immediate mode for a component. + * + * @see com.vaadin.terminal.VariableOwner#setImmediate() + * + * @param immediate + * new mode for the component + */ + public void setImmediate(boolean immediate) { + this.immediate = immediate; + } + + /** + * Returns true if the component has user-defined styles. + * + * @return true if the component has user-defined styles + */ + public boolean hasStyles() { + return styles != null && !styles.isEmpty(); + } + + /** + * Returns true if the component is enabled. + * + * @see com.vaadin.ui.Component#isEnabled() + * + * @return true if the component is enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Enables or disables the component. + * + * @see com.vaadin.ui.Component#setEnabled(boolean) + * + * @param enabled + * new mode for the component + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Gets the description of the component (typically shown as tooltip). + * + * @see com.vaadin.ui.AbstractComponent#getDescription() + * + * @return component description (not null, can be empty string) + */ + public String getDescription() { + return description; + } + + /** + * Sets the description of the component (typically shown as tooltip). + * + * @see com.vaadin.ui.AbstractComponent#setDescription(String) + * + * @param description + * new component description (can be null) + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Returns true if the component has a description. + * + * @return true if the component has a description + */ + public boolean hasDescription() { + return getDescription() != null && !"".equals(getDescription()); + } + + /** + * Gets the caption of the component (typically shown by the containing + * layout). + * + * @see com.vaadin.ui.Component#getCaption() + * + * @return component caption - can be null (no caption) or empty string + * (reserve space for an empty caption) + */ + public String getCaption() { + return caption; + } + + /** + * Sets the caption of the component (typically shown by the containing + * layout). + * + * @see com.vaadin.ui.Component#setCaption(String) + * + * @param caption + * new component caption - can be null (no caption) or empty + * string (reserve space for an empty caption) + */ + public void setCaption(String caption) { + this.caption = caption; + } + + /** + * Returns the visibility state of the component. Note that this state is + * related to the component only, not its parent. This might differ from + * what {@link Component#isVisible()} returns as this takes the hierarchy + * into account. + * + * @return The visibility state. + */ + public boolean isVisible() { + return visible; + } + + /** + * Sets the visibility state of the component. + * + * @param visible + * The new visibility state. + */ + public void setVisible(boolean visible) { + this.visible = visible; + } + + public URLReference getIcon() { + return icon; + } + + public void setIcon(URLReference icon) { + this.icon = icon; + } + + /** + * Gets the style names for the component. + * + * @return A List of style names or null if no styles have been set. + */ + public List<String> getStyles() { + return styles; + } + + /** + * Sets the style names for the component. + * + * @param styles + * A list containing style names + */ + public void setStyles(List<String> styles) { + this.styles = styles; + } + + /** + * Gets the debug id for the component. The debugId is added as DOM id for + * the component. + * + * @return The debug id for the component or null if not set + */ + public String getDebugId() { + return debugId; + } + + /** + * Sets the debug id for the component. The debugId is added as DOM id for + * the component. + * + * @param debugId + * The new debugId for the component or null for no debug id + * + */ + public void setDebugId(String debugId) { + this.debugId = debugId; + } + + /** + * Gets the identifiers for the event listeners that have been registered + * for the component (using an event id) + * + * @return A set of event identifiers or null if no identifiers have been + * registered + */ + public Set<String> getRegisteredEventListeners() { + return registeredEventListeners; + } + + /** + * Sets the identifiers for the event listeners that have been registered + * for the component (using an event id) + * + * @param registeredEventListeners + * The new set of identifiers or null if no identifiers have been + * registered + */ + public void setRegisteredEventListeners(Set<String> registeredEventListeners) { + this.registeredEventListeners = registeredEventListeners; + } + + /** + * Adds an event listener id. + * + * @param eventListenerId + * The event identifier to add + */ + public void addRegisteredEventListener(String eventListenerId) { + if (registeredEventListeners == null) { + registeredEventListeners = new HashSet<String>(); + } + registeredEventListeners.add(eventListenerId); + + } + + /** + * Removes an event listener id. + * + * @param eventListenerId + * The event identifier to remove + */ + public void removeRegisteredEventListener(String eventIdentifier) { + if (registeredEventListeners == null) { + return; + } + registeredEventListeners.remove(eventIdentifier); + if (registeredEventListeners.size() == 0) { + registeredEventListeners = null; + } + } + + /** + * Returns the current error message for the component. + * + * @return HTML formatted error message to show for the component or null if + * none + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * Sets the current error message for the component. + * + * TODO this could use an object with more details about the error + * + * @param errorMessage + * HTML formatted error message to show for the component or null + * for none + */ + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ComputedStyle.java b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java index e994e47d63..bedb217c63 100644 --- a/src/com/vaadin/terminal/gwt/client/ComputedStyle.java +++ b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java @@ -4,7 +4,7 @@ package com.vaadin.terminal.gwt.client; import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.user.client.Element; +import com.google.gwt.dom.client.Element; public class ComputedStyle { diff --git a/src/com/vaadin/terminal/gwt/client/Connector.java b/src/com/vaadin/terminal/gwt/client/Connector.java new file mode 100644 index 0000000000..1c61052735 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/Connector.java @@ -0,0 +1,49 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import java.io.Serializable; + +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.server.ClientConnector; + +/** + * Interface implemented by all classes that are capable of communicating with + * the server or the client side. + * <p> + * A connector consists of a shared state (server sets the state and + * automatically communicates changes to the client) and the possibility to do + * RPC calls either from the server to the client or from the client to the + * server. + * </p> + * <p> + * No classes should implement this interface directly, client side classes + * wanting to communicate with server side should implement + * {@link ServerConnector} and server side classes should implement + * {@link ClientConnector}. + * </p> + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public interface Connector extends Serializable { + /** + * Gets the current shared state of the connector. + * + * @since 7.0. + * @return state The shared state object. Can be any sub type of + * {@link SharedState}. Never null. + */ + public SharedState getState(); + + /** + * Returns the id for this connector. This is set by the framework and does + * not change during the lifetime of a connector. + * + * @return The id for the connector. + */ + public String getConnectorId(); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangeEvent.java b/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangeEvent.java new file mode 100644 index 0000000000..aa41caf75d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangeEvent.java @@ -0,0 +1,97 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import java.io.Serializable; +import java.util.List; + +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; +import com.vaadin.terminal.gwt.client.communication.AbstractServerConnectorEvent; + +/** + * Event for containing data related to a change in the {@link ServerConnector} + * hierarchy. A {@link ConnectorHierarchyChangedEvent} is fired when an update + * from the server has been fully processed and all hierarchy updates have been + * completed. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class ConnectorHierarchyChangeEvent extends + AbstractServerConnectorEvent<ConnectorHierarchyChangeHandler> { + /** + * Type of this event, used by the event bus. + */ + public static final Type<ConnectorHierarchyChangeHandler> TYPE = new Type<ConnectorHierarchyChangeHandler>(); + + List<ComponentConnector> oldChildren; + private ComponentContainerConnector parent; + + public ConnectorHierarchyChangeEvent() { + } + + /** + * Returns a collection of the old children for the connector. This was the + * state before the update was received from the server. + * + * @return A collection of old child connectors. Never returns null. + */ + public List<ComponentConnector> getOldChildren() { + return oldChildren; + } + + /** + * Sets the collection of the old children for the connector. + * + * @param oldChildren + * The old child connectors. Must not be null. + */ + public void setOldChildren(List<ComponentConnector> oldChildren) { + this.oldChildren = oldChildren; + } + + /** + * Returns the {@link ComponentContainerConnector} for which this event + * occurred. + * + * @return The {@link ComponentContainerConnector} whose child collection + * has changed. Never returns null. + */ + public ComponentContainerConnector getParent() { + return parent; + } + + /** + * Sets the {@link ComponentContainerConnector} for which this event + * occurred. + * + * @param The + * {@link ComponentContainerConnector} whose child collection has + * changed. + */ + public void setParent(ComponentContainerConnector parent) { + this.parent = parent; + } + + public interface ConnectorHierarchyChangeHandler extends Serializable, + EventHandler { + public void onConnectorHierarchyChange( + ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent); + } + + @Override + public void dispatch(ConnectorHierarchyChangeHandler handler) { + handler.onConnectorHierarchyChange(this); + } + + @Override + public GwtEvent.Type<ConnectorHierarchyChangeHandler> getAssociatedType() { + return TYPE; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ConnectorMap.java b/src/com/vaadin/terminal/gwt/client/ConnectorMap.java new file mode 100644 index 0000000000..e57776cf1c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ConnectorMap.java @@ -0,0 +1,250 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.ui.Widget; + +public class ConnectorMap { + + private Map<String, ServerConnector> idToConnector = new HashMap<String, ServerConnector>(); + + public static ConnectorMap get(ApplicationConnection applicationConnection) { + return applicationConnection.getConnectorMap(); + } + + @Deprecated + private final ComponentDetailMap idToComponentDetail = ComponentDetailMap + .create(); + + /** + * Returns a {@link ServerConnector} by its id + * + * @param id + * The connector id + * @return A connector or null if a connector with the given id has not been + * registered + */ + public ServerConnector getConnector(String connectorId) { + return idToConnector.get(connectorId); + } + + /** + * Returns a {@link ComponentConnector} element by its root element + * + * @param element + * Root element of the {@link ComponentConnector} + * @return A connector or null if a connector with the given id has not been + * registered + */ + public ComponentConnector getConnector(Element element) { + return (ComponentConnector) getConnector(getConnectorId(element)); + } + + /** + * FIXME: What does this even do and why? + * + * @param pid + * @return + */ + public boolean isDragAndDropPaintable(String pid) { + return (pid.startsWith("DD")); + } + + /** + * Checks if a connector with the given id has been registered. + * + * @param connectorId + * The id to check for + * @return true if a connector has been registered with the given id, false + * otherwise + */ + public boolean hasConnector(String connectorId) { + return idToConnector.containsKey(connectorId); + } + + /** + * Removes all registered connectors + */ + public void clear() { + idToConnector.clear(); + idToComponentDetail.clear(); + } + + /** + * Retrieves the connector whose widget matches the parameter. + * + * @param widget + * The widget + * @return A connector with {@literal widget} as its root widget or null if + * no connector was found + */ + public ComponentConnector getConnector(Widget widget) { + return getConnector(widget.getElement()); + } + + public void registerConnector(String id, ServerConnector connector) { + ComponentDetail componentDetail = GWT.create(ComponentDetail.class); + idToComponentDetail.put(id, componentDetail); + idToConnector.put(id, connector); + if (connector instanceof ComponentConnector) { + ComponentConnector pw = (ComponentConnector) connector; + setConnectorId(pw.getWidget().getElement(), id); + } + } + + private native void setConnectorId(Element el, String id) + /*-{ + el.tkPid = id; + }-*/; + + /** + * Gets the connector id using a DOM element - the element should be the + * root element for a connector, otherwise no id will be found. Use + * {@link #getConnectorId(ServerConnector)} instead whenever possible. + * + * @see #getConnectorId(ServerConnector) + * @param el + * element of the connector whose id is desired + * @return the id of the element's connector, if it's a connector + */ + native String getConnectorId(Element el) + /*-{ + return el.tkPid; + }-*/; + + /** + * Gets the main element for the connector with the given id. The reverse of + * {@link #getConnectorId(Element)}. + * + * @param connectorId + * the id of the widget whose element is desired + * @return the element for the connector corresponding to the id + */ + public Element getElement(String connectorId) { + ServerConnector p = getConnector(connectorId); + if (p instanceof ComponentConnector) { + return ((ComponentConnector) p).getWidget().getElement(); + } + + return null; + } + + /** + * Unregisters the given connector; always use after removing a connector. + * This method does not remove the connector from the DOM, but marks the + * connector so that ApplicationConnection may clean up its references to + * it. Removing the widget from DOM is component containers responsibility. + * + * @param connector + * the connector to remove + */ + public void unregisterConnector(ServerConnector connector) { + if (connector == null) { + VConsole.error("Trying to unregister null connector"); + return; + } + + String connectorId = connector.getConnectorId(); + + idToComponentDetail.remove(connectorId); + idToConnector.remove(connectorId); + connector.onUnregister(); + + if (connector instanceof ComponentContainerConnector) { + for (ComponentConnector child : ((ComponentContainerConnector) connector) + .getChildren()) { + if (child.getParent() == connector) { + // Only unregister children that are actually connected to + // this parent. For instance when moving connectors from one + // layout to another and removing the first layout it will + // still contain references to its old children, which are + // now attached to another connector. + unregisterConnector(child); + } + } + } + } + + /** + * Gets all registered {@link ComponentConnector} instances + * + * @return An array of all registered {@link ComponentConnector} instances + */ + public ComponentConnector[] getComponentConnectors() { + ArrayList<ComponentConnector> result = new ArrayList<ComponentConnector>(); + + for (ServerConnector connector : getConnectors()) { + if (connector instanceof ComponentConnector) { + result.add((ComponentConnector) connector); + } + } + + return result.toArray(new ComponentConnector[result.size()]); + } + + @Deprecated + private ComponentDetail getComponentDetail( + ComponentConnector componentConnector) { + return idToComponentDetail.get(componentConnector.getConnectorId()); + } + + public int size() { + return idToConnector.size(); + } + + /** + * FIXME: Should be moved to VAbstractPaintableWidget + * + * @param paintable + * @return + */ + @Deprecated + public TooltipInfo getTooltipInfo(ComponentConnector paintable, Object key) { + return getComponentDetail(paintable).getTooltipInfo(key); + } + + @Deprecated + public TooltipInfo getWidgetTooltipInfo(Widget widget, Object key) { + return getTooltipInfo(getConnector(widget), key); + } + + public Collection<? extends ServerConnector> getConnectors() { + return Collections.unmodifiableCollection(idToConnector.values()); + } + + /** + * FIXME: Should not be here + * + * @param componentConnector + * @return + */ + @Deprecated + public void registerTooltip(ComponentConnector componentConnector, + Object key, TooltipInfo tooltip) { + getComponentDetail(componentConnector).putAdditionalTooltip(key, + tooltip); + + } + + /** + * Tests if the widget is the root widget of a {@link ComponentConnector}. + * + * @param widget + * The widget to test + * @return true if the widget is the root widget of a + * {@link ComponentConnector}, false otherwise + */ + public boolean isConnector(Widget w) { + return getConnectorId(w.getElement()) != null; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/Console.java b/src/com/vaadin/terminal/gwt/client/Console.java index 483ab8e0fd..8db145342a 100644 --- a/src/com/vaadin/terminal/gwt/client/Console.java +++ b/src/com/vaadin/terminal/gwt/client/Console.java @@ -22,8 +22,8 @@ public interface Console { public abstract void printLayoutProblems(ValueMap meta, ApplicationConnection applicationConnection, - Set<Paintable> zeroHeightComponents, - Set<Paintable> zeroWidthComponents); + Set<ComponentConnector> zeroHeightComponents, + Set<ComponentConnector> zeroWidthComponents); public abstract void setQuietMode(boolean quietDebugMode); diff --git a/src/com/vaadin/terminal/gwt/client/Container.java b/src/com/vaadin/terminal/gwt/client/Container.java deleted file mode 100644 index 83f104886f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/Container.java +++ /dev/null @@ -1,71 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client; - -import java.util.Set; - -import com.google.gwt.user.client.ui.Widget; - -public interface Container extends Paintable { - - /** - * Replace child of this layout with another component. - * - * Each layout must be able to switch children. To to this, one must just - * give references to a current and new child. - * - * @param oldComponent - * Child to be replaced - * @param newComponent - * Child that replaces the oldComponent - */ - void replaceChildComponent(Widget oldComponent, Widget newComponent); - - /** - * Is a given component child of this layout. - * - * @param component - * Component to test. - * @return true iff component is a child of this layout. - */ - boolean hasChildComponent(Widget component); - - /** - * Update child components caption, description and error message. - * - * <p> - * Each component is responsible for maintaining its caption, description - * and error message. In most cases components doesn't want to do that and - * those elements reside outside of the component. Because of this layouts - * must provide service for it's childen to show those elements for them. - * </p> - * - * @param component - * Child component for which service is requested. - * @param uidl - * UIDL of the child component. - */ - void updateCaption(Paintable component, UIDL uidl); - - /** - * Called when a child components size has been updated in the rendering - * phase. - * - * @param children - * Set of child widgets whose size have changed - * @return true if the size of the Container remains the same, false if the - * event need to be propagated to the Containers parent - */ - boolean requestLayout(Set<Paintable> children); - - /** - * Returns the size currently allocated for the child component. - * - * @param child - * @return - */ - RenderSpace getAllocatedSpace(Widget child); - -} diff --git a/src/com/vaadin/terminal/gwt/client/DateTimeService.java b/src/com/vaadin/terminal/gwt/client/DateTimeService.java index c0151d2819..45ba4a7452 100644 --- a/src/com/vaadin/terminal/gwt/client/DateTimeService.java +++ b/src/com/vaadin/terminal/gwt/client/DateTimeService.java @@ -8,7 +8,7 @@ import java.util.Date; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.LocaleInfo; -import com.vaadin.terminal.gwt.client.ui.VDateField; +import com.vaadin.terminal.gwt.client.ui.datefield.VDateField; /** * This class provides date/time parsing services to all components on the diff --git a/src/com/vaadin/terminal/gwt/client/DirectionalManagedLayout.java b/src/com/vaadin/terminal/gwt/client/DirectionalManagedLayout.java new file mode 100644 index 0000000000..296fbb22ff --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/DirectionalManagedLayout.java @@ -0,0 +1,12 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import com.vaadin.terminal.gwt.client.ui.ManagedLayout; + +public interface DirectionalManagedLayout extends ManagedLayout { + public void layoutVertically(); + + public void layoutHorizontally(); +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/EventHelper.java b/src/com/vaadin/terminal/gwt/client/EventHelper.java index 600baf8c9d..95f5125f1b 100644 --- a/src/com/vaadin/terminal/gwt/client/EventHelper.java +++ b/src/com/vaadin/terminal/gwt/client/EventHelper.java @@ -6,10 +6,12 @@ package com.vaadin.terminal.gwt.client; import static com.vaadin.terminal.gwt.client.EventId.BLUR; import static com.vaadin.terminal.gwt.client.EventId.FOCUS; +import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.DomEvent.Type; +import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.HasBlurHandlers; -import com.google.gwt.event.dom.client.HasFocusHandlers; +import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.HandlerRegistration; /** @@ -40,38 +42,56 @@ import com.google.gwt.event.shared.HandlerRegistration; */ public class EventHelper { - public static HandlerRegistration updateFocusHandler(Paintable paintable, - ApplicationConnection client, - HandlerRegistration handlerRegistration) { - if (client.hasEventListeners(paintable, FOCUS)) { - if (handlerRegistration == null) { - handlerRegistration = ((HasFocusHandlers) paintable) - .addFocusHandler((FocusHandler) paintable); - } - return handlerRegistration; - } else if (handlerRegistration != null) { - handlerRegistration.removeHandler(); - handlerRegistration = null; + /** + * Adds or removes a focus handler depending on if the connector has focus + * listeners on the server side or not. + * + * @param connector + * The connector to update. Must implement focusHandler. + * @param handlerRegistration + * The old registration reference or null no handler has been + * registered previously + * @return a new registration handler that can be used to unregister the + * handler later + */ + public static <T extends ComponentConnector & FocusHandler> HandlerRegistration updateFocusHandler( + T connector, HandlerRegistration handlerRegistration) { + return updateHandler(connector, FOCUS, handlerRegistration, + FocusEvent.getType()); + } - } - return null; + /** + * Adds or removes a blur handler depending on if the connector has blur + * listeners on the server side or not. + * + * @param connector + * The connector to update. Must implement BlurHandler. + * @param handlerRegistration + * The old registration reference or null no handler has been + * registered previously + * @return a new registration handler that can be used to unregister the + * handler later + */ + public static <T extends ComponentConnector & BlurHandler> HandlerRegistration updateBlurHandler( + T connector, HandlerRegistration handlerRegistration) { + return updateHandler(connector, BLUR, handlerRegistration, + BlurEvent.getType()); } - public static HandlerRegistration updateBlurHandler(Paintable paintable, - ApplicationConnection client, - HandlerRegistration handlerRegistration) { - if (client.hasEventListeners(paintable, BLUR)) { + private static <H extends EventHandler> HandlerRegistration updateHandler( + ComponentConnector connector, String eventIdentifier, + HandlerRegistration handlerRegistration, Type<H> type) { + if (connector.hasEventListener(eventIdentifier)) { if (handlerRegistration == null) { - handlerRegistration = ((HasBlurHandlers) paintable) - .addBlurHandler((BlurHandler) paintable); + handlerRegistration = connector.getWidget().addDomHandler( + (H) connector, type); } - return handlerRegistration; } else if (handlerRegistration != null) { handlerRegistration.removeHandler(); handlerRegistration = null; - } - return null; + return handlerRegistration; + } } diff --git a/src/com/vaadin/terminal/gwt/client/EventId.java b/src/com/vaadin/terminal/gwt/client/EventId.java index ae2f5ddeb3..d3ef2e4e7e 100644 --- a/src/com/vaadin/terminal/gwt/client/EventId.java +++ b/src/com/vaadin/terminal/gwt/client/EventId.java @@ -6,5 +6,4 @@ package com.vaadin.terminal.gwt.client; public interface EventId { public static final String BLUR = "blur"; public static final String FOCUS = "focus"; - public static final String LAYOUT_CLICK = "layout_click"; } diff --git a/src/com/vaadin/terminal/gwt/client/FastStringSet.java b/src/com/vaadin/terminal/gwt/client/FastStringSet.java new file mode 100644 index 0000000000..05ed8addc8 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/FastStringSet.java @@ -0,0 +1,60 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; + +public final class FastStringSet extends JavaScriptObject { + protected FastStringSet() { + // JSO constructor + } + + public native boolean contains(String string) + /*-{ + return this.hasOwnProperty(string); + }-*/; + + public native void add(String string) + /*-{ + this[string] = true; + }-*/; + + public native void addAll(JsArrayString array) + /*-{ + for(var i = 0; i < array.length; i++) { + this[array[i]] = true; + } + }-*/; + + public native JsArrayString dump() + /*-{ + var array = []; + for(var string in this) { + if (this.hasOwnProperty(string)) { + array.push(string); + } + } + return array; + }-*/; + + public native void remove(String string) + /*-{ + delete this[string]; + }-*/; + + public native boolean isEmpty() + /*-{ + for(var string in this) { + if (this.hasOwnProperty(string)) { + return false; + } + } + return true; + }-*/; + + public static FastStringSet create() { + return JavaScriptObject.createObject().cast(); + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/HistoryImplIEVaadin.java b/src/com/vaadin/terminal/gwt/client/HistoryImplIEVaadin.java deleted file mode 100644 index 0571959339..0000000000 --- a/src/com/vaadin/terminal/gwt/client/HistoryImplIEVaadin.java +++ /dev/null @@ -1,192 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -/* - * Copyright 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.terminal.gwt.client; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.impl.HistoryImpl; - -/** - * A slightly modified version of GWT's HistoryImplIE6 to bypass bug #2931. Also - * combined with HistoryImplFrame. - * - * This class should be removed if GWT issue 3890 gets resolved. (Also remember - * to removed deferred binding rule from .gwt.xml file). - */ -public class HistoryImplIEVaadin extends HistoryImpl { - - private static native Element findHistoryFrame() - /*-{ - return $doc.getElementById('__gwt_historyFrame'); - }-*/; - - private static native Element getTokenElement(Element historyFrame) - /*-{ - // Initialize the history iframe. If '__gwt_historyToken' already exists, then - // we're probably backing into the app, so _don't_ set the iframe's location. - if (historyFrame.contentWindow) { - var doc = historyFrame.contentWindow.document; - return doc.getElementById('__gwt_historyToken'); - } - }-*/; - - protected Element historyFrame; - - @Override - protected final void nativeUpdate(String historyToken) { - /* - * Must update the location hash since it isn't already correct. - */ - updateHash(historyToken); - navigateFrame(historyToken); - } - - @Override - protected final void nativeUpdateOnEvent(String historyToken) { - updateHash(historyToken); - } - - /** - * Sanitizes an untrusted string to be used in an HTML context. NOTE: This - * method of escaping strings should only be used on Internet Explorer. - * - * @param maybeHtml - * untrusted string that may contain html - * @return sanitized string - */ - private static String escapeHtml(String maybeHtml) { - final Element div = DOM.createDiv(); - DOM.setInnerText(div, maybeHtml); - return DOM.getInnerHTML(div); - } - - /** - * For IE6, reading from $wnd.location.hash drops part of the fragment if - * the fragment contains a '?'. To avoid this bug, we use location.href - * instead. - */ - private static native String getLocationHash() - /*-{ - var href = $wnd.location.href; - var hashLoc = href.lastIndexOf("#"); - return (hashLoc > 0) ? href.substring(hashLoc) : ""; - }-*/; - - @Override - public boolean init() { - historyFrame = findHistoryFrame(); - if (historyFrame == null) { - return false; - } - - initHistoryToken(); - - // Initialize the history iframe. If a token element already exists, - // then - // we're probably backing into the app, so _don't_ create a new item. - Element tokenElement = getTokenElement(historyFrame); - if (tokenElement != null) { - setToken(getTokenElementContent(tokenElement)); - } else { - navigateFrame(getToken()); - } - - injectGlobalHandler(); - - initUrlCheckTimer(); - return true; - } - - protected native String getTokenElementContent(Element tokenElement) - /*-{ - return tokenElement.innerText; - }-*/; - - protected native void initHistoryToken() - /*-{ - // Assume an empty token. - var token = ''; - // Get the initial token from the url's hash component. - var hash = @com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::getLocationHash()(); - if (hash.length > 0) { - try { - token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1)); - } catch (e) { - // Clear the bad hash (this can't have been a valid token). - $wnd.location.hash = ''; - } - } - @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token); - }-*/; - - protected native void injectGlobalHandler() - /*-{ - var historyImplRef = this; - - $wnd.__gwt_onHistoryLoad = $entry(function(token) { - historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token); - }); - }-*/; - - protected native void navigateFrame(String token) - /*-{ - var escaped = @com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::escapeHtml(Ljava/lang/String;)(token); - var doc = this.@com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::historyFrame.contentWindow.document; - doc.open(); - doc.write('<html><body onload="if(parent.__gwt_onHistoryLoad)parent.__gwt_onHistoryLoad(__gwt_historyToken.innerText)"><div id="__gwt_historyToken">' + escaped + '</div></body></html>'); - doc.close(); - }-*/; - - protected native void updateHash(String token) - /*-{ - $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(token); - }-*/; - - private native void initUrlCheckTimer() - /*-{ - // This is the URL check timer. It detects when an unexpected change - // occurs in the document's URL (e.g. when the user enters one manually - // or selects a 'favorite', but only the #hash part changes). When this - // occurs, we _must_ reload the page. This is because IE has a really - // nasty bug that totally mangles its history stack and causes the location - // bar in the UI to stop working under these circumstances. - var historyImplRef = this; - var urlChecker = function() { - $wnd.setTimeout(urlChecker, 250); - var hash = @com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::getLocationHash()(); - if (hash.length > 0) { - var token = ''; - try { - token = historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1)); - } catch (e) { - // If there's a bad hash, always reload. This could only happen if - // if someone entered or linked to a bad url. - $wnd.location.reload(); - } - - var historyToken = @com.google.gwt.user.client.impl.HistoryImpl::getToken()(); - if (token != historyToken) { - $wnd.location.reload(); - } - } - }; - urlChecker(); - }-*/; - -} diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManager.java b/src/com/vaadin/terminal/gwt/client/LayoutManager.java new file mode 100644 index 0000000000..9390c719fc --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java @@ -0,0 +1,1191 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.user.client.Timer; +import com.vaadin.terminal.gwt.client.MeasuredSize.MeasureResult; +import com.vaadin.terminal.gwt.client.ui.ManagedLayout; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener; +import com.vaadin.terminal.gwt.client.ui.layout.LayoutDependencyTree; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; + +public class LayoutManager { + private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop."; + + private static final boolean debugLogging = false; + + private ApplicationConnection connection; + private final Set<Element> measuredNonConnectorElements = new HashSet<Element>(); + private final MeasuredSize nullSize = new MeasuredSize(); + + private LayoutDependencyTree currentDependencyTree; + + private final Collection<ManagedLayout> needsHorizontalLayout = new HashSet<ManagedLayout>(); + private final Collection<ManagedLayout> needsVerticalLayout = new HashSet<ManagedLayout>(); + + private final Collection<ComponentConnector> needsMeasure = new HashSet<ComponentConnector>(); + + private Collection<ComponentConnector> pendingOverflowFixes = new HashSet<ComponentConnector>(); + + private final Map<Element, Collection<ElementResizeListener>> elementResizeListeners = new HashMap<Element, Collection<ElementResizeListener>>(); + private final Set<Element> listenersToFire = new HashSet<Element>(); + + private boolean layoutPending = false; + private Timer layoutTimer = new Timer() { + @Override + public void run() { + cancel(); + layoutNow(); + } + }; + private boolean everythingNeedsMeasure = false; + + public void setConnection(ApplicationConnection connection) { + if (this.connection != null) { + throw new RuntimeException( + "LayoutManager connection can never be changed"); + } + this.connection = connection; + } + + /** + * Gets the layout manager associated with the given + * {@link ApplicationConnection}. + * + * @param connection + * the application connection to get a layout manager for + * @return the layout manager associated with the provided application + * connection + */ + public static LayoutManager get(ApplicationConnection connection) { + return connection.getLayoutManager(); + } + + /** + * Registers that a ManagedLayout is depending on the size of an Element. + * This causes this layout manager to measure the element in the beginning + * of every layout phase and call the appropriate layout method of the + * managed layout if the size of the element has changed. + * + * @param owner + * the ManagedLayout that depends on an element + * @param element + * the Element that should be measured + */ + public void registerDependency(ManagedLayout owner, Element element) { + MeasuredSize measuredSize = ensureMeasured(element); + setNeedsLayout(owner); + measuredSize.addDependent(owner.getConnectorId()); + } + + private MeasuredSize ensureMeasured(Element element) { + MeasuredSize measuredSize = getMeasuredSize(element, null); + if (measuredSize == null) { + measuredSize = new MeasuredSize(); + + if (ConnectorMap.get(connection).getConnector(element) == null) { + measuredNonConnectorElements.add(element); + } + setMeasuredSize(element, measuredSize); + } + return measuredSize; + } + + private boolean needsMeasure(Element e) { + if (connection.getConnectorMap().getConnectorId(e) != null) { + return true; + } else if (elementResizeListeners.containsKey(e)) { + return true; + } else if (getMeasuredSize(e, nullSize).hasDependents()) { + return true; + } else { + return false; + } + } + + /** + * Assigns a measured size to an element. Method defined as protected to + * allow separate implementation for IE8 in which delete not always works. + * + * @param element + * the dom element to attach the measured size to + * @param measuredSize + * the measured size to attach to the element. If + * <code>null</code>, any previous measured size is removed. + */ + protected native void setMeasuredSize(Element element, + MeasuredSize measuredSize) + /*-{ + if (measuredSize) { + element.vMeasuredSize = measuredSize; + } else { + delete element.vMeasuredSize; + } + }-*/; + + private static native final MeasuredSize getMeasuredSize(Element element, + MeasuredSize defaultSize) + /*-{ + return element.vMeasuredSize || defaultSize; + }-*/; + + private final MeasuredSize getMeasuredSize(ComponentConnector connector) { + Element element = connector.getWidget().getElement(); + MeasuredSize measuredSize = getMeasuredSize(element, null); + if (measuredSize == null) { + measuredSize = new MeasuredSize(); + setMeasuredSize(element, measuredSize); + } + return measuredSize; + } + + /** + * Registers that a ManagedLayout is no longer depending on the size of an + * Element. + * + * @see #registerDependency(ManagedLayout, Element) + * + * @param owner + * the ManagedLayout no longer depends on an element + * @param element + * the Element that that no longer needs to be measured + */ + public void unregisterDependency(ManagedLayout owner, Element element) { + MeasuredSize measuredSize = getMeasuredSize(element, null); + if (measuredSize == null) { + return; + } + measuredSize.removeDependent(owner.getConnectorId()); + stopMeasuringIfUnecessary(element); + } + + public boolean isLayoutRunning() { + return currentDependencyTree != null; + } + + private void countLayout(Map<ManagedLayout, Integer> layoutCounts, + ManagedLayout layout) { + Integer count = layoutCounts.get(layout); + if (count == null) { + count = Integer.valueOf(0); + } else { + count = Integer.valueOf(count.intValue() + 1); + } + layoutCounts.put(layout, count); + if (count.intValue() > 2) { + VConsole.error(Util.getConnectorString(layout) + + " has been layouted " + count.intValue() + " times"); + } + } + + private void layoutLater() { + if (!layoutPending) { + layoutPending = true; + layoutTimer.schedule(100); + } + } + + public void layoutNow() { + if (isLayoutRunning()) { + throw new IllegalStateException( + "Can't start a new layout phase before the previous layout phase ends."); + } + layoutPending = false; + try { + currentDependencyTree = new LayoutDependencyTree(); + doLayout(); + } finally { + currentDependencyTree = null; + } + } + + private void doLayout() { + VConsole.log("Starting layout phase"); + + Map<ManagedLayout, Integer> layoutCounts = new HashMap<ManagedLayout, Integer>(); + + int passes = 0; + Duration totalDuration = new Duration(); + + for (ManagedLayout layout : needsHorizontalLayout) { + currentDependencyTree.setNeedsHorizontalLayout(layout, true); + } + for (ManagedLayout layout : needsVerticalLayout) { + currentDependencyTree.setNeedsVerticalLayout(layout, true); + } + needsHorizontalLayout.clear(); + needsVerticalLayout.clear(); + + for (ComponentConnector connector : needsMeasure) { + currentDependencyTree.setNeedsMeasure(connector, true); + } + needsMeasure.clear(); + + measureNonConnectors(); + + VConsole.log("Layout init in " + totalDuration.elapsedMillis() + " ms"); + + while (true) { + Duration passDuration = new Duration(); + passes++; + + int measuredConnectorCount = measureConnectors( + currentDependencyTree, everythingNeedsMeasure); + everythingNeedsMeasure = false; + if (measuredConnectorCount == 0) { + VConsole.log("No more changes in pass " + passes); + break; + } + + int measureTime = passDuration.elapsedMillis(); + VConsole.log(" Measured " + measuredConnectorCount + + " elements in " + measureTime + " ms"); + + if (!listenersToFire.isEmpty()) { + for (Element element : listenersToFire) { + Collection<ElementResizeListener> listeners = elementResizeListeners + .get(element); + ElementResizeListener[] array = listeners + .toArray(new ElementResizeListener[listeners.size()]); + ElementResizeEvent event = new ElementResizeEvent(this, + element); + for (ElementResizeListener listener : array) { + try { + listener.onElementResize(event); + } catch (RuntimeException e) { + VConsole.error(e); + } + } + } + int measureListenerTime = passDuration.elapsedMillis(); + VConsole.log(" Fired resize listeners for " + + listenersToFire.size() + " elements in " + + (measureListenerTime - measureTime) + " ms"); + measureTime = measuredConnectorCount; + listenersToFire.clear(); + } + + FastStringSet updatedSet = FastStringSet.create(); + + while (currentDependencyTree.hasHorizontalConnectorToLayout() + || currentDependencyTree.hasVerticaConnectorToLayout()) { + for (ManagedLayout layout : currentDependencyTree + .getHorizontalLayoutTargets()) { + if (layout instanceof DirectionalManagedLayout) { + currentDependencyTree + .markAsHorizontallyLayouted(layout); + DirectionalManagedLayout cl = (DirectionalManagedLayout) layout; + try { + cl.layoutHorizontally(); + } catch (RuntimeException e) { + VConsole.log(e); + } + countLayout(layoutCounts, cl); + } else { + currentDependencyTree + .markAsHorizontallyLayouted(layout); + currentDependencyTree.markAsVerticallyLayouted(layout); + SimpleManagedLayout rr = (SimpleManagedLayout) layout; + try { + rr.layout(); + } catch (RuntimeException e) { + VConsole.log(e); + } + countLayout(layoutCounts, rr); + } + if (debugLogging) { + updatedSet.add(layout.getConnectorId()); + } + } + + for (ManagedLayout layout : currentDependencyTree + .getVerticalLayoutTargets()) { + if (layout instanceof DirectionalManagedLayout) { + currentDependencyTree.markAsVerticallyLayouted(layout); + DirectionalManagedLayout cl = (DirectionalManagedLayout) layout; + try { + cl.layoutVertically(); + } catch (RuntimeException e) { + VConsole.log(e); + } + countLayout(layoutCounts, cl); + } else { + currentDependencyTree + .markAsHorizontallyLayouted(layout); + currentDependencyTree.markAsVerticallyLayouted(layout); + SimpleManagedLayout rr = (SimpleManagedLayout) layout; + try { + rr.layout(); + } catch (RuntimeException e) { + VConsole.log(e); + } + countLayout(layoutCounts, rr); + } + if (debugLogging) { + updatedSet.add(layout.getConnectorId()); + } + } + } + + if (debugLogging) { + JsArrayString changedCids = updatedSet.dump(); + + StringBuilder b = new StringBuilder(" "); + b.append(changedCids.length()); + b.append(" requestLayout invocations in "); + b.append(passDuration.elapsedMillis() - measureTime); + b.append(" ms"); + if (changedCids.length() < 30) { + for (int i = 0; i < changedCids.length(); i++) { + if (i != 0) { + b.append(", "); + } else { + b.append(": "); + } + String connectorString = changedCids.get(i); + if (changedCids.length() < 10) { + ServerConnector connector = ConnectorMap.get( + connection).getConnector(connectorString); + connectorString = Util + .getConnectorString(connector); + } + b.append(connectorString); + } + } + VConsole.log(b.toString()); + } + + VConsole.log("Pass " + passes + " completed in " + + passDuration.elapsedMillis() + " ms"); + + if (passes > 100) { + VConsole.log(LOOP_ABORT_MESSAGE); + VNotification.createNotification(VNotification.DELAY_FOREVER) + .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED, + "error"); + break; + } + } + + int postLayoutStart = totalDuration.elapsedMillis(); + for (ComponentConnector connector : connection.getConnectorMap() + .getComponentConnectors()) { + if (connector instanceof PostLayoutListener) { + ((PostLayoutListener) connector).postLayout(); + } + } + VConsole.log("Invoke post layout listeners in " + + (totalDuration.elapsedMillis() - postLayoutStart) + " ms"); + + VConsole.log("Total layout phase time: " + + totalDuration.elapsedMillis() + "ms"); + } + + private void logConnectorStatus(int connectorId) { + currentDependencyTree + .logDependencyStatus((ComponentConnector) ConnectorMap.get( + connection).getConnector(Integer.toString(connectorId))); + } + + private int measureConnectors(LayoutDependencyTree layoutDependencyTree, + boolean measureAll) { + if (!pendingOverflowFixes.isEmpty()) { + Duration duration = new Duration(); + + HashMap<Element, String> originalOverflows = new HashMap<Element, String>(); + + HashSet<ComponentConnector> delayedOverflowFixes = new HashSet<ComponentConnector>(); + + // First set overflow to hidden (and save previous value so it can + // be restored later) + for (ComponentConnector componentConnector : pendingOverflowFixes) { + // Delay the overflow fix if the involved connectors might still + // change + if (!currentDependencyTree + .noMoreChangesExpected(componentConnector) + || !currentDependencyTree + .noMoreChangesExpected(componentConnector + .getParent())) { + delayedOverflowFixes.add(componentConnector); + continue; + } + + if (debugLogging) { + VConsole.log("Doing overflow fix for " + + Util.getConnectorString(componentConnector) + + " in " + + Util.getConnectorString(componentConnector + .getParent())); + } + + Element parentElement = componentConnector.getWidget() + .getElement().getParentElement(); + Style style = parentElement.getStyle(); + String originalOverflow = style.getOverflow(); + + if (originalOverflow != null + && !originalOverflows.containsKey(parentElement)) { + // Store original value for restore, but only the first time + // the value is changed + originalOverflows.put(parentElement, originalOverflow); + } + + style.setOverflow(Overflow.HIDDEN); + } + + pendingOverflowFixes.removeAll(delayedOverflowFixes); + + // Then ensure all scrolling elements are reflowed by measuring + for (ComponentConnector componentConnector : pendingOverflowFixes) { + componentConnector.getWidget().getElement().getParentElement() + .getOffsetHeight(); + } + + // Finally restore old overflow value and update bookkeeping + for (ComponentConnector componentConnector : pendingOverflowFixes) { + Element parentElement = componentConnector.getWidget() + .getElement().getParentElement(); + parentElement.getStyle().setProperty("overflow", + originalOverflows.get(parentElement)); + + layoutDependencyTree.setNeedsMeasure(componentConnector, true); + } + if (!pendingOverflowFixes.isEmpty()) { + VConsole.log("Did overflow fix for " + + pendingOverflowFixes.size() + " elements in " + + duration.elapsedMillis() + " ms"); + } + pendingOverflowFixes = delayedOverflowFixes; + } + + int measureCount = 0; + if (measureAll) { + ComponentConnector[] connectors = ConnectorMap.get(connection) + .getComponentConnectors(); + for (ComponentConnector connector : connectors) { + measueConnector(connector); + } + for (ComponentConnector connector : connectors) { + layoutDependencyTree.setNeedsMeasure(connector, false); + } + measureCount += connectors.length; + } + + while (layoutDependencyTree.hasConnectorsToMeasure()) { + Collection<ComponentConnector> measureTargets = layoutDependencyTree + .getMeasureTargets(); + for (ComponentConnector connector : measureTargets) { + measueConnector(connector); + measureCount++; + } + for (ComponentConnector connector : measureTargets) { + layoutDependencyTree.setNeedsMeasure(connector, false); + } + } + return measureCount; + } + + private void measueConnector(ComponentConnector connector) { + Element element = connector.getWidget().getElement(); + MeasuredSize measuredSize = getMeasuredSize(connector); + MeasureResult measureResult = measuredAndUpdate(element, measuredSize); + + if (measureResult.isChanged()) { + onConnectorChange(connector, measureResult.isWidthChanged(), + measureResult.isHeightChanged()); + } + } + + private void onConnectorChange(ComponentConnector connector, + boolean widthChanged, boolean heightChanged) { + setNeedsOverflowFix(connector); + if (heightChanged) { + currentDependencyTree.markHeightAsChanged(connector); + } + if (widthChanged) { + currentDependencyTree.markWidthAsChanged(connector); + } + } + + private void setNeedsOverflowFix(ComponentConnector connector) { + // IE9 doesn't need the original fix, but for some reason it needs this + if (BrowserInfo.get().requiresOverflowAutoFix() + || BrowserInfo.get().isIE9()) { + ComponentConnector scrollingBoundary = currentDependencyTree + .getScrollingBoundary(connector); + if (scrollingBoundary != null) { + pendingOverflowFixes.add(scrollingBoundary); + } + } + } + + private void measureNonConnectors() { + for (Element element : measuredNonConnectorElements) { + measuredAndUpdate(element, getMeasuredSize(element, null)); + } + VConsole.log("Measured " + measuredNonConnectorElements.size() + + " non connector elements"); + } + + private MeasureResult measuredAndUpdate(Element element, + MeasuredSize measuredSize) { + MeasureResult measureResult = measuredSize.measure(element); + if (measureResult.isChanged()) { + notifyListenersAndDepdendents(element, + measureResult.isWidthChanged(), + measureResult.isHeightChanged()); + } + return measureResult; + } + + private void notifyListenersAndDepdendents(Element element, + boolean widthChanged, boolean heightChanged) { + assert widthChanged || heightChanged; + + MeasuredSize measuredSize = getMeasuredSize(element, nullSize); + JsArrayString dependents = measuredSize.getDependents(); + for (int i = 0; i < dependents.length(); i++) { + String pid = dependents.get(i); + ManagedLayout dependent = (ManagedLayout) connection + .getConnectorMap().getConnector(pid); + if (dependent != null) { + if (heightChanged) { + currentDependencyTree.setNeedsVerticalLayout(dependent, + true); + } + if (widthChanged) { + currentDependencyTree.setNeedsHorizontalLayout(dependent, + true); + } + } + } + if (elementResizeListeners.containsKey(element)) { + listenersToFire.add(element); + } + } + + private static boolean isManagedLayout(ComponentConnector connector) { + return connector instanceof ManagedLayout; + } + + public void forceLayout() { + ConnectorMap connectorMap = connection.getConnectorMap(); + ComponentConnector[] componentConnectors = connectorMap + .getComponentConnectors(); + for (ComponentConnector connector : componentConnectors) { + if (connector instanceof ManagedLayout) { + setNeedsLayout((ManagedLayout) connector); + } + } + setEverythingNeedsMeasure(); + layoutNow(); + } + + /** + * Marks that a ManagedLayout should be layouted in the next layout phase + * even if none of the elements managed by the layout have been resized. + * + * @param layout + * the managed layout that should be layouted + */ + public final void setNeedsLayout(ManagedLayout layout) { + setNeedsHorizontalLayout(layout); + setNeedsVerticalLayout(layout); + } + + /** + * Marks that a ManagedLayout should be layouted horizontally in the next + * layout phase even if none of the elements managed by the layout have been + * resized horizontally. + * + * For SimpleManagedLayout which is always layouted in both directions, this + * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + * + * @param layout + * the managed layout that should be layouted + */ + public final void setNeedsHorizontalLayout(ManagedLayout layout) { + needsHorizontalLayout.add(layout); + } + + /** + * Marks that a ManagedLayout should be layouted vertically in the next + * layout phase even if none of the elements managed by the layout have been + * resized vertically. + * + * For SimpleManagedLayout which is always layouted in both directions, this + * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + * + * @param layout + * the managed layout that should be layouted + */ + public final void setNeedsVerticalLayout(ManagedLayout layout) { + needsVerticalLayout.add(layout); + } + + /** + * Gets the outer height (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured outer height (including margins, paddings and + * borders) of the element in pixels. + */ + public final int getOuterHeight(Element element) { + return getMeasuredSize(element, nullSize).getOuterHeight(); + } + + /** + * Gets the outer width (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured outer width (including margins, paddings and + * borders) of the element in pixels. + */ + public final int getOuterWidth(Element element) { + return getMeasuredSize(element, nullSize).getOuterWidth(); + } + + /** + * Gets the inner height (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured inner height (excluding margins, paddings and + * borders) of the element in pixels. + */ + public final int getInnerHeight(Element element) { + return getMeasuredSize(element, nullSize).getInnerHeight(); + } + + /** + * Gets the inner width (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured inner width (excluding margins, paddings and + * borders) of the element in pixels. + */ + public final int getInnerWidth(Element element) { + return getMeasuredSize(element, nullSize).getInnerWidth(); + } + + /** + * Gets the border height (top border + bottom border) of the given element, + * provided that it has been measured. These elements are guaranteed to be + * measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured border height (top border + bottom border) of the + * element in pixels. + */ + public final int getBorderHeight(Element element) { + return getMeasuredSize(element, nullSize).getBorderHeight(); + } + + /** + * Gets the padding height (top padding + bottom padding) of the given + * element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured padding height (top padding + bottom padding) of the + * element in pixels. + */ + public int getPaddingHeight(Element element) { + return getMeasuredSize(element, nullSize).getPaddingHeight(); + } + + /** + * Gets the border width (left border + right border) of the given element, + * provided that it has been measured. These elements are guaranteed to be + * measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured border width (left border + right border) of the + * element in pixels. + */ + public int getBorderWidth(Element element) { + return getMeasuredSize(element, nullSize).getBorderWidth(); + } + + /** + * Gets the padding width (left padding + right padding) of the given + * element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured padding width (left padding + right padding) of the + * element in pixels. + */ + public int getPaddingWidth(Element element) { + return getMeasuredSize(element, nullSize).getPaddingWidth(); + } + + /** + * Gets the top padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top padding of the element in pixels. + */ + public int getPaddingTop(Element element) { + return getMeasuredSize(element, nullSize).getPaddingTop(); + } + + /** + * Gets the left padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left padding of the element in pixels. + */ + public int getPaddingLeft(Element element) { + return getMeasuredSize(element, nullSize).getPaddingLeft(); + } + + /** + * Gets the bottom padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom padding of the element in pixels. + */ + public int getPaddingBottom(Element element) { + return getMeasuredSize(element, nullSize).getPaddingBottom(); + } + + /** + * Gets the right padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right padding of the element in pixels. + */ + public int getPaddingRight(Element element) { + return getMeasuredSize(element, nullSize).getPaddingRight(); + } + + /** + * Gets the top margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top margin of the element in pixels. + */ + public int getMarginTop(Element element) { + return getMeasuredSize(element, nullSize).getMarginTop(); + } + + /** + * Gets the right margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right margin of the element in pixels. + */ + public int getMarginRight(Element element) { + return getMeasuredSize(element, nullSize).getMarginRight(); + } + + /** + * Gets the bottom margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom margin of the element in pixels. + */ + public int getMarginBottom(Element element) { + return getMeasuredSize(element, nullSize).getMarginBottom(); + } + + /** + * Gets the left margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left margin of the element in pixels. + */ + public int getMarginLeft(Element element) { + return getMeasuredSize(element, nullSize).getMarginLeft(); + } + + /** + * Registers the outer height (including margins, borders and paddings) of a + * component. This can be used as an optimization by ManagedLayouts; by + * informing the LayoutManager about what size a component will have, the + * layout propagation can continue directly without first measuring the + * potentially resized elements. + * + * @param component + * the component for which the size is reported + * @param outerHeight + * the new outer height (including margins, borders and paddings) + * of the component in pixels + */ + public void reportOuterHeight(ComponentConnector component, int outerHeight) { + MeasuredSize measuredSize = getMeasuredSize(component); + if (isLayoutRunning()) { + boolean heightChanged = measuredSize.setOuterHeight(outerHeight); + + if (heightChanged) { + onConnectorChange(component, false, true); + notifyListenersAndDepdendents(component.getWidget() + .getElement(), false, true); + } + currentDependencyTree.setNeedsVerticalMeasure(component, false); + } else if (measuredSize.getOuterHeight() != outerHeight) { + setNeedsMeasure(component); + } + } + + /** + * Registers the height reserved for a relatively sized component. This can + * be used as an optimization by ManagedLayouts; by informing the + * LayoutManager about what size a component will have, the layout + * propagation can continue directly without first measuring the potentially + * resized elements. + * + * @param component + * the relatively sized component for which the size is reported + * @param assignedHeight + * the inner height of the relatively sized component's parent + * element in pixels + */ + public void reportHeightAssignedToRelative(ComponentConnector component, + int assignedHeight) { + assert component.isRelativeHeight(); + + float percentSize = parsePercent(component.getState().getHeight()); + int effectiveHeight = Math.round(assignedHeight * (percentSize / 100)); + + reportOuterHeight(component, effectiveHeight); + } + + /** + * Registers the width reserved for a relatively sized component. This can + * be used as an optimization by ManagedLayouts; by informing the + * LayoutManager about what size a component will have, the layout + * propagation can continue directly without first measuring the potentially + * resized elements. + * + * @param component + * the relatively sized component for which the size is reported + * @param assignedWidth + * the inner width of the relatively sized component's parent + * element in pixels + */ + public void reportWidthAssignedToRelative(ComponentConnector component, + int assignedWidth) { + assert component.isRelativeWidth(); + + float percentSize = parsePercent(component.getState().getWidth()); + int effectiveWidth = Math.round(assignedWidth * (percentSize / 100)); + + reportOuterWidth(component, effectiveWidth); + } + + private static float parsePercent(String size) { + return Float.parseFloat(size.substring(0, size.length() - 1)); + } + + /** + * Registers the outer width (including margins, borders and paddings) of a + * component. This can be used as an optimization by ManagedLayouts; by + * informing the LayoutManager about what size a component will have, the + * layout propagation can continue directly without first measuring the + * potentially resized elements. + * + * @param component + * the component for which the size is reported + * @param outerWidth + * the new outer width (including margins, borders and paddings) + * of the component in pixels + */ + public void reportOuterWidth(ComponentConnector component, int outerWidth) { + MeasuredSize measuredSize = getMeasuredSize(component); + if (isLayoutRunning()) { + boolean widthChanged = measuredSize.setOuterWidth(outerWidth); + + if (widthChanged) { + onConnectorChange(component, true, false); + notifyListenersAndDepdendents(component.getWidget() + .getElement(), true, false); + } + currentDependencyTree.setNeedsHorizontalMeasure(component, false); + } else if (measuredSize.getOuterWidth() != outerWidth) { + setNeedsMeasure(component); + } + } + + /** + * Adds a listener that will be notified whenever the size of a specific + * element changes. Adding a listener to an element also ensures that all + * sizes for that element will be available starting from the next layout + * phase. + * + * @param element + * the element that should be checked for size changes + * @param listener + * an ElementResizeListener that will be informed whenever the + * size of the target element has changed + */ + public void addElementResizeListener(Element element, + ElementResizeListener listener) { + Collection<ElementResizeListener> listeners = elementResizeListeners + .get(element); + if (listeners == null) { + listeners = new HashSet<ElementResizeListener>(); + elementResizeListeners.put(element, listeners); + ensureMeasured(element); + } + listeners.add(listener); + } + + /** + * Removes an element resize listener from the provided element. This might + * cause this LayoutManager to stop tracking the size of the element if no + * other sources are interested in the size. + * + * @param element + * the element to which the element resize listener was + * previously added + * @param listener + * the ElementResizeListener that should no longer get informed + * about size changes to the target element. + */ + public void removeElementResizeListener(Element element, + ElementResizeListener listener) { + Collection<ElementResizeListener> listeners = elementResizeListeners + .get(element); + if (listeners != null) { + listeners.remove(listener); + if (listeners.isEmpty()) { + elementResizeListeners.remove(element); + stopMeasuringIfUnecessary(element); + } + } + } + + private void stopMeasuringIfUnecessary(Element element) { + if (!needsMeasure(element)) { + measuredNonConnectorElements.remove(element); + setMeasuredSize(element, null); + } + } + + /** + * Informs this LayoutManager that the size of a component might have + * changed. If there is no upcoming layout phase, a new layout phase is + * scheduled. This method should be used whenever a size might have changed + * from outside of Vaadin's normal update phase, e.g. when an icon has been + * loaded or when the user resizes some part of the UI using the mouse. + * + * @param component + * the component whose size might have changed. + */ + public void setNeedsMeasure(ComponentConnector component) { + if (isLayoutRunning()) { + currentDependencyTree.setNeedsMeasure(component, true); + } else { + needsMeasure.add(component); + layoutLater(); + } + } + + public void setEverythingNeedsMeasure() { + everythingNeedsMeasure = true; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java new file mode 100644 index 0000000000..2b677985b5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java @@ -0,0 +1,23 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import com.google.gwt.dom.client.Element; + +public class LayoutManagerIE8 extends LayoutManager { + + @Override + protected native void setMeasuredSize(Element element, + MeasuredSize measuredSize) + // IE8 cannot do delete element.vMeasuredSize, at least in the case when + // element is not attached to the document (e.g. when a caption is removed) + /*-{ + if (measuredSize) { + element.vMeasuredSize = measuredSize; + } else { + element.vMeasuredSize = undefined; + } + }-*/; + +} diff --git a/src/com/vaadin/terminal/gwt/client/MeasuredSize.java b/src/com/vaadin/terminal/gwt/client/MeasuredSize.java new file mode 100644 index 0000000000..97822fa8ec --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/MeasuredSize.java @@ -0,0 +1,228 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.dom.client.Element; + +public class MeasuredSize { + public static class MeasureResult { + private final boolean widthChanged; + private final boolean heightChanged; + + private MeasureResult(boolean widthChanged, boolean heightChanged) { + this.widthChanged = widthChanged; + this.heightChanged = heightChanged; + } + + public boolean isHeightChanged() { + return heightChanged; + } + + public boolean isWidthChanged() { + return widthChanged; + } + + public boolean isChanged() { + return heightChanged || widthChanged; + } + } + + private int width = -1; + private int height = -1; + + private int[] paddings = new int[4]; + private int[] borders = new int[4]; + private int[] margins = new int[4]; + + private FastStringSet dependents = FastStringSet.create(); + + public int getOuterHeight() { + return height; + } + + public int getOuterWidth() { + return width; + } + + public void addDependent(String pid) { + dependents.add(pid); + } + + public void removeDependent(String pid) { + dependents.remove(pid); + } + + public boolean hasDependents() { + return !dependents.isEmpty(); + } + + public JsArrayString getDependents() { + return dependents.dump(); + } + + private static int sumWidths(int[] sizes) { + return sizes[1] + sizes[3]; + } + + private static int sumHeights(int[] sizes) { + return sizes[0] + sizes[2]; + } + + public int getInnerHeight() { + return height - sumHeights(margins) - sumHeights(borders) + - sumHeights(paddings); + } + + public int getInnerWidth() { + return width - sumWidths(margins) - sumWidths(borders) + - sumWidths(paddings); + } + + public boolean setOuterHeight(int height) { + if (this.height != height) { + this.height = height; + return true; + } else { + return false; + } + } + + public boolean setOuterWidth(int width) { + if (this.width != width) { + this.width = width; + return true; + } else { + return false; + } + } + + public int getBorderHeight() { + return sumHeights(borders); + } + + public int getBorderWidth() { + return sumWidths(borders); + } + + public int getPaddingHeight() { + return sumHeights(paddings); + } + + public int getPaddingWidth() { + return sumWidths(paddings); + } + + public int getMarginHeight() { + return sumHeights(margins); + } + + public int getMarginWidth() { + return sumWidths(margins); + } + + public int getMarginTop() { + return margins[0]; + } + + public int getMarginRight() { + return margins[1]; + } + + public int getMarginBottom() { + return margins[2]; + } + + public int getMarginLeft() { + return margins[3]; + } + + public int getBorderTop() { + return margins[0]; + } + + public int getBorderRight() { + return margins[1]; + } + + public int getBorderBottom() { + return margins[2]; + } + + public int getBorderLeft() { + return margins[3]; + } + + public int getPaddingTop() { + return paddings[0]; + } + + public int getPaddingRight() { + return paddings[1]; + } + + public int getPaddingBottom() { + return paddings[2]; + } + + public int getPaddingLeft() { + return paddings[3]; + } + + public MeasureResult measure(Element element) { + boolean heightChanged = false; + boolean widthChanged = false; + + ComputedStyle computedStyle = new ComputedStyle(element); + int[] paddings = computedStyle.getPadding(); + if (!heightChanged && hasHeightChanged(this.paddings, paddings)) { + heightChanged = true; + } + if (!widthChanged && hasWidthChanged(this.paddings, paddings)) { + widthChanged = true; + } + this.paddings = paddings; + + int[] margins = computedStyle.getMargin(); + if (!heightChanged && hasHeightChanged(this.margins, margins)) { + heightChanged = true; + } + if (!widthChanged && hasWidthChanged(this.margins, margins)) { + widthChanged = true; + } + this.margins = margins; + + int[] borders = computedStyle.getBorder(); + if (!heightChanged && hasHeightChanged(this.borders, borders)) { + heightChanged = true; + } + if (!widthChanged && hasWidthChanged(this.borders, borders)) { + widthChanged = true; + } + this.borders = borders; + + int requiredHeight = Util.getRequiredHeight(element); + int marginHeight = sumHeights(margins); + if (setOuterHeight(requiredHeight + marginHeight)) { + heightChanged = true; + } + + int requiredWidth = Util.getRequiredWidth(element); + int marginWidth = sumWidths(margins); + if (setOuterWidth(requiredWidth + marginWidth)) { + widthChanged = true; + } + + return new MeasureResult(widthChanged, heightChanged); + } + + private static boolean hasWidthChanged(int[] sizes1, int[] sizes2) { + return sizes1[1] != sizes2[1] || sizes1[3] != sizes2[3]; + } + + private static boolean hasHeightChanged(int[] sizes1, int[] sizes2) { + return sizes1[0] != sizes2[0] || sizes1[2] != sizes2[2]; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java b/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java index 260dfa6fff..f5ff707eed 100644 --- a/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java +++ b/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java @@ -5,19 +5,18 @@ package com.vaadin.terminal.gwt.client; import java.io.Serializable; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.user.client.Event; - /** * Helper class to store and transfer mouse event details. */ public class MouseEventDetails implements Serializable { - public static final int BUTTON_LEFT = Event.BUTTON_LEFT; - public static final int BUTTON_MIDDLE = Event.BUTTON_MIDDLE; - public static final int BUTTON_RIGHT = Event.BUTTON_RIGHT; + // From com.google.gwt.dom.client.NativeEvent + public static final int BUTTON_LEFT = 1; + public static final int BUTTON_MIDDLE = 4; + public static final int BUTTON_RIGHT = 2; private static final char DELIM = ','; + // From com.google.gwt.user.client.Event + private static final int ONDBLCLICK = 0x00002; private int button; private int clientX; @@ -66,26 +65,47 @@ public class MouseEventDetails implements Serializable { return relativeY; } - public MouseEventDetails(NativeEvent evt) { - this(evt, null); + public void setButton(int button) { + this.button = button; } - public MouseEventDetails(NativeEvent evt, Element relativeToElement) { - type = Event.getTypeInt(evt.getType()); - clientX = Util.getTouchOrMouseClientX(evt); - clientY = Util.getTouchOrMouseClientY(evt); - button = evt.getButton(); - altKey = evt.getAltKey(); - ctrlKey = evt.getCtrlKey(); - metaKey = evt.getMetaKey(); - shiftKey = evt.getShiftKey(); - if (relativeToElement != null) { - relativeX = getRelativeX(clientX, relativeToElement); - relativeY = getRelativeY(clientY, relativeToElement); - } + public void setClientX(int clientX) { + this.clientX = clientX; + } + + public void setClientY(int clientY) { + this.clientY = clientY; + } + + public void setAltKey(boolean altKey) { + this.altKey = altKey; + } + + public void setCtrlKey(boolean ctrlKey) { + this.ctrlKey = ctrlKey; + } + + public void setMetaKey(boolean metaKey) { + this.metaKey = metaKey; + } + + public void setShiftKey(boolean shiftKey) { + this.shiftKey = shiftKey; + } + + public void setType(int type) { + this.type = type; } - private MouseEventDetails() { + public void setRelativeX(int relativeX) { + this.relativeX = relativeX; + } + + public void setRelativeY(int relativeY) { + this.relativeY = relativeY; + } + + public MouseEventDetails() { } @Override @@ -128,22 +148,12 @@ public class MouseEventDetails implements Serializable { return ""; } - public Class<MouseEventDetails> getType() { - return MouseEventDetails.class; + public int getType() { + return type; } public boolean isDoubleClick() { - return type == Event.ONDBLCLICK; - } - - private static int getRelativeX(int clientX, Element target) { - return clientX - target.getAbsoluteLeft() + target.getScrollLeft() - + target.getOwnerDocument().getScrollLeft(); - } - - private static int getRelativeY(int clientY, Element target) { - return clientY - target.getAbsoluteTop() + target.getScrollTop() - + target.getOwnerDocument().getScrollTop(); + return type == ONDBLCLICK; } } diff --git a/src/com/vaadin/terminal/gwt/client/MouseEventDetailsBuilder.java b/src/com/vaadin/terminal/gwt/client/MouseEventDetailsBuilder.java new file mode 100644 index 0000000000..58dd488351 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/MouseEventDetailsBuilder.java @@ -0,0 +1,74 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.user.client.Event; + +/** + * Helper class for constructing a MouseEventDetails object from a + * {@link NativeEvent}. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class MouseEventDetailsBuilder { + + /** + * Construct a {@link MouseEventDetails} object from the given event + * + * @param evt + * The event to use as a source for the details + * @return a MouseEventDetails containing information from the event + */ + public static MouseEventDetails buildMouseEventDetails(NativeEvent evt) { + return buildMouseEventDetails(evt, null); + } + + /** + * Construct a {@link MouseEventDetails} object from the given event + * + * @param evt + * The event to use as a source for the details + * @param relativeToElement + * The element whose position + * {@link MouseEventDetails#getRelativeX()} and + * {@link MouseEventDetails#getRelativeY()} are relative to. + * @return a MouseEventDetails containing information from the event + */ + public static MouseEventDetails buildMouseEventDetails(NativeEvent evt, + Element relativeToElement) { + MouseEventDetails mouseEventDetails = new MouseEventDetails(); + mouseEventDetails.setType(Event.getTypeInt(evt.getType())); + mouseEventDetails.setClientX(Util.getTouchOrMouseClientX(evt)); + mouseEventDetails.setClientY(Util.getTouchOrMouseClientY(evt)); + mouseEventDetails.setButton(evt.getButton()); + mouseEventDetails.setAltKey(evt.getAltKey()); + mouseEventDetails.setCtrlKey(evt.getCtrlKey()); + mouseEventDetails.setMetaKey(evt.getMetaKey()); + mouseEventDetails.setShiftKey(evt.getShiftKey()); + if (relativeToElement != null) { + mouseEventDetails.setRelativeX(getRelativeX( + mouseEventDetails.getClientX(), relativeToElement)); + mouseEventDetails.setRelativeY(getRelativeY( + mouseEventDetails.getClientY(), relativeToElement)); + } + return mouseEventDetails; + + } + + private static int getRelativeX(int clientX, Element target) { + return clientX - target.getAbsoluteLeft() + target.getScrollLeft() + + target.getOwnerDocument().getScrollLeft(); + } + + private static int getRelativeY(int clientY, Element target) { + return clientY - target.getAbsoluteTop() + target.getScrollTop() + + target.getOwnerDocument().getScrollTop(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/NullConsole.java b/src/com/vaadin/terminal/gwt/client/NullConsole.java index 12df4b323b..2d15ffd46c 100644 --- a/src/com/vaadin/terminal/gwt/client/NullConsole.java +++ b/src/com/vaadin/terminal/gwt/client/NullConsole.java @@ -32,8 +32,8 @@ public class NullConsole implements Console { public void printLayoutProblems(ValueMap meta, ApplicationConnection applicationConnection, - Set<Paintable> zeroHeightComponents, - Set<Paintable> zeroWidthComponents) { + Set<ComponentConnector> zeroHeightComponents, + Set<ComponentConnector> zeroWidthComponents) { } public void log(Throwable e) { @@ -41,7 +41,8 @@ public class NullConsole implements Console { } public void error(Throwable e) { - GWT.log(e.getMessage(), e); + // Borrow exception handling from VDebugConsole + VDebugConsole.handleError(e, this); } public void setQuietMode(boolean quietDebugMode) { diff --git a/src/com/vaadin/terminal/gwt/client/Paintable.java b/src/com/vaadin/terminal/gwt/client/Paintable.java index 62abeab5a0..c9e3ef79cc 100644 --- a/src/com/vaadin/terminal/gwt/client/Paintable.java +++ b/src/com/vaadin/terminal/gwt/client/Paintable.java @@ -12,6 +12,7 @@ package com.vaadin.terminal.gwt.client; * Updates can be sent back to the server using the * {@link ApplicationConnection#updateVariable()} methods. */ +@Deprecated public interface Paintable { public void updateFromUIDL(UIDL uidl, ApplicationConnection client); diff --git a/src/com/vaadin/terminal/gwt/client/ServerConnector.java b/src/com/vaadin/terminal/gwt/client/ServerConnector.java new file mode 100644 index 0000000000..b331f1f07d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ServerConnector.java @@ -0,0 +1,104 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import java.util.Collection; + +import com.google.gwt.event.shared.GwtEvent; +import com.google.web.bindery.event.shared.HandlerRegistration; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; + +/** + * Interface implemented by all client side classes that can be communicate with + * the server. Classes implementing this interface are initialized by the + * framework when needed and have the ability to communicate with the server. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public interface ServerConnector extends Connector { + + /** + * Sets a new state for the connector. + * + * @param state + * The new state + * @deprecated This should be removed. Framework should update what is + * returned by getState() instead of setting a new state object. + * Note that this must be done either so that setState accepts a + * state object once (first time received from the server) or + * getState() in AbstractConnector uses a generated class to + * create the state object (like RpcProy.craete()) + */ + @Deprecated + public void setState(SharedState state); + + /** + * Gets ApplicationConnection instance that created this connector. + * + * @return The ApplicationConnection as set by + * {@link #doInit(String, ApplicationConnection)} + */ + public ApplicationConnection getConnection(); + + /** + * Tests whether the connector is enabled or not. This method checks that + * the connector is enabled in context, i.e. if the parent connector is + * disabled, this method must return false. + * + * @return true if the connector is enabled, false otherwise + */ + public boolean isEnabled(); + + /** + * + * Called once by the framework to initialize the connector. + * <p> + * Note that the shared state is not yet available at this point nor any + * hierarchy information. + */ + public void doInit(String connectorId, ApplicationConnection connection); + + /** + * For internal use by the framework: returns the registered RPC + * implementations for an RPC interface identifier. + * + * TODO interface identifier type or format may change + * + * @param rpcInterfaceId + * RPC interface identifier: fully qualified interface type name + * @return RPC interface implementations registered for an RPC interface, + * not null + */ + public <T extends ClientRpc> Collection<T> getRpcImplementations( + String rpcInterfaceId); + + /** + * Adds a handler that is called whenever some part of the state has been + * updated by the server. + * + * @param handler + * The handler that should be added. + * @return A handler registration reference that can be used to unregister + * the handler + */ + public HandlerRegistration addStateChangeHandler(StateChangeHandler handler); + + /** + * Sends the given event to all registered handlers. + * + * @param event + * The event to send. + */ + public void fireEvent(GwtEvent<?> event); + + /** + * Event called when connector has been unregistered. + */ + public void onUnregister(); + +} diff --git a/src/com/vaadin/terminal/gwt/client/SimpleTree.java b/src/com/vaadin/terminal/gwt/client/SimpleTree.java index 017884c94f..350e0d707d 100644 --- a/src/com/vaadin/terminal/gwt/client/SimpleTree.java +++ b/src/com/vaadin/terminal/gwt/client/SimpleTree.java @@ -28,6 +28,7 @@ public class SimpleTree extends ComplexPanel { Style style = getElement().getStyle(); style.setProperty("whiteSpace", "nowrap"); style.setPadding(3, Unit.PX); + style.setPaddingLeft(12, Unit.PX); style = handle.getStyle(); style.setDisplay(Display.NONE); @@ -43,7 +44,7 @@ public class SimpleTree extends ComplexPanel { getElement().appendChild(handle); getElement().appendChild(text); style = children.getStyle(); - style.setPaddingLeft(20, Unit.PX); + style.setPaddingLeft(9, Unit.PX); style.setDisplay(Display.NONE); getElement().appendChild(children); @@ -109,7 +110,7 @@ public class SimpleTree extends ComplexPanel { protected void add(Widget child, Element container) { super.add(child, container); handle.getStyle().setDisplay(Display.INLINE_BLOCK); - + getElement().getStyle().setPaddingLeft(3, Unit.PX); } } diff --git a/src/com/vaadin/terminal/gwt/client/TooltipInfo.java b/src/com/vaadin/terminal/gwt/client/TooltipInfo.java index 6f8ddd5237..fb33a56c56 100644 --- a/src/com/vaadin/terminal/gwt/client/TooltipInfo.java +++ b/src/com/vaadin/terminal/gwt/client/TooltipInfo.java @@ -7,7 +7,7 @@ public class TooltipInfo { private String title; - private UIDL errorUidl; + private String errorMessageHtml; public TooltipInfo() { } @@ -24,12 +24,12 @@ public class TooltipInfo { this.title = title; } - public UIDL getErrorUidl() { - return errorUidl; + public String getErrorMessage() { + return errorMessageHtml; } - public void setErrorUidl(UIDL errorUidl) { - this.errorUidl = errorUidl; + public void setErrorMessage(String errorMessage) { + errorMessageHtml = errorMessage; } } diff --git a/src/com/vaadin/terminal/gwt/client/UIDL.java b/src/com/vaadin/terminal/gwt/client/UIDL.java index a6298af8d1..a523016b60 100644 --- a/src/com/vaadin/terminal/gwt/client/UIDL.java +++ b/src/com/vaadin/terminal/gwt/client/UIDL.java @@ -16,7 +16,7 @@ import com.vaadin.ui.Component; /** * When a component is updated, it's client side widget's - * {@link Paintable#updateFromUIDL(UIDL, ApplicationConnection) + * {@link ComponentConnector#updateFromUIDL(UIDL, ApplicationConnection) * updateFromUIDL()} will be called with the updated ("changes") UIDL received * from the server. * <p> @@ -55,7 +55,7 @@ public final class UIDL extends JavaScriptObject { * AbstractComponent.paintContent()}. Note that if the UIDL corresponds to a * Paintable, a component identifier will be returned instead - this is used * internally and is not needed within - * {@link Paintable#updateFromUIDL(UIDL, ApplicationConnection) + * {@link ComponentConnector#updateFromUIDL(UIDL, ApplicationConnection) * updateFromUIDL()}. * * @return the name for this section @@ -493,17 +493,6 @@ public final class UIDL extends JavaScriptObject { return this.length - 2; }-*/; - /** - * Shorthand that returns the component errors as UIDL. Only applicable for - * Paintables. - * - * @return the error UIDL if available - */ - public native UIDL getErrors() - /*-{ - return this[1]['error']; - }-*/; - native boolean isMapAttribute(String name) /*-{ return typeof this[1][name] == "object"; @@ -516,9 +505,10 @@ public final class UIDL extends JavaScriptObject { * the name of the attribute * @return the Paintable referenced by the attribute, if it exists */ - public Paintable getPaintableAttribute(String name, + public ServerConnector getPaintableAttribute(String name, ApplicationConnection connection) { - return connection.getPaintable(getStringAttribute(name)); + return ConnectorMap.get(connection).getConnector( + getStringAttribute(name)); } /** @@ -528,9 +518,10 @@ public final class UIDL extends JavaScriptObject { * the name of the variable * @return the Paintable referenced by the variable, if it exists */ - public Paintable getPaintableVariable(String name, + public ServerConnector getPaintableVariable(String name, ApplicationConnection connection) { - return connection.getPaintable(getStringVariable(name)); + return ConnectorMap.get(connection).getConnector( + getStringVariable(name)); } /** diff --git a/src/com/vaadin/terminal/gwt/client/Util.java b/src/com/vaadin/terminal/gwt/client/Util.java index b9baf362e4..bfe63caefd 100644 --- a/src/com/vaadin/terminal/gwt/client/Util.java +++ b/src/com/vaadin/terminal/gwt/client/Util.java @@ -5,16 +5,13 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; +import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; -import java.util.Map; -import java.util.Set; +import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; @@ -26,12 +23,12 @@ import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; -import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; public class Util { @@ -67,30 +64,6 @@ public class Util { return el; }-*/; - private static final int LAZY_SIZE_CHANGE_TIMEOUT = 400; - private static Set<Paintable> latelyChangedWidgets = new HashSet<Paintable>(); - - private static Timer lazySizeChangeTimer = new Timer() { - private boolean lazySizeChangeTimerScheduled = false; - - @Override - public void run() { - componentSizeUpdated(latelyChangedWidgets); - latelyChangedWidgets.clear(); - lazySizeChangeTimerScheduled = false; - } - - @Override - public void schedule(int delayMillis) { - if (lazySizeChangeTimerScheduled) { - cancel(); - } else { - lazySizeChangeTimerScheduled = true; - } - super.schedule(delayMillis); - } - }; - /** * This helper method can be called if components size have been changed * outside rendering phase. It notifies components parent about the size @@ -106,61 +79,37 @@ public class Util { * @param widget * @param lazy * run componentSizeUpdated lazyly - */ - public static void notifyParentOfSizeChange(Paintable widget, boolean lazy) { - if (lazy) { - latelyChangedWidgets.add(widget); - lazySizeChangeTimer.schedule(LAZY_SIZE_CHANGE_TIMEOUT); - } else { - Set<Paintable> widgets = new HashSet<Paintable>(); - widgets.add(widget); - Util.componentSizeUpdated(widgets); - } - } - - /** - * Called when the size of one or more widgets have changed during - * rendering. Finds parent container and notifies them of the size change. * - * @param paintables + * @deprecated since 7.0, use + * {@link LayoutManager#setNeedsMeasure(ComponentConnector)} + * instead */ - public static void componentSizeUpdated(Set<Paintable> paintables) { - if (paintables.isEmpty()) { - return; + @Deprecated + public static void notifyParentOfSizeChange(Widget widget, boolean lazy) { + ComponentConnector connector = findConnectorFor(widget); + if (connector != null) { + connector.getLayoutManager().setNeedsMeasure(connector); + if (!lazy) { + connector.getLayoutManager().layoutNow(); + } } + } - Map<Container, Set<Paintable>> childWidgets = new HashMap<Container, Set<Paintable>>(); - - for (Paintable paintable : paintables) { - Widget widget = (Widget) paintable; - if (!widget.isAttached()) { + private static ComponentConnector findConnectorFor(Widget widget) { + List<ApplicationConnection> runningApplications = ApplicationConfiguration + .getRunningApplications(); + for (ApplicationConnection applicationConnection : runningApplications) { + ConnectorMap connectorMap = applicationConnection.getConnectorMap(); + ComponentConnector connector = connectorMap.getConnector(widget); + if (connector == null) { continue; } - - // ApplicationConnection.getConsole().log( - // "Widget " + Util.getSimpleName(widget) + " size updated"); - Widget parent = widget.getParent(); - while (parent != null && !(parent instanceof Container)) { - parent = parent.getParent(); - } - if (parent != null) { - Set<Paintable> set = childWidgets.get(parent); - if (set == null) { - set = new HashSet<Paintable>(); - childWidgets.put((Container) parent, set); - } - set.add(paintable); - } - } - - Set<Paintable> parentChanges = new HashSet<Paintable>(); - for (Container parent : childWidgets.keySet()) { - if (!parent.requestLayout(childWidgets.get(parent))) { - parentChanges.add(parent); + if (connector.getConnection() == applicationConnection) { + return connector; } } - componentSizeUpdated(parentChanges); + return null; } public static float parseRelativeSize(String size) { @@ -176,68 +125,6 @@ public class Util { } } - /** - * Returns closest parent Widget in hierarchy that implements Container - * interface - * - * @param component - * @return closest parent Container - */ - public static Container getLayout(Widget component) { - Widget parent = component.getParent(); - while (parent != null && !(parent instanceof Container)) { - parent = parent.getParent(); - } - if (parent != null) { - assert ((Container) parent).hasChildComponent(component); - - return (Container) parent; - } - return null; - } - - /** - * Detects if current browser is IE. - * - * @deprecated use BrowserInfo class instead - * - * @return true if IE - */ - @Deprecated - public static boolean isIE() { - return BrowserInfo.get().isIE(); - } - - /** - * Detects if current browser is IE6. - * - * @deprecated use BrowserInfo class instead - * - * @return true if IE6 - */ - @Deprecated - public static boolean isIE6() { - return BrowserInfo.get().isIE6(); - } - - /** - * @deprecated use BrowserInfo class instead - * @return - */ - @Deprecated - public static boolean isIE7() { - return BrowserInfo.get().isIE7(); - } - - /** - * @deprecated use BrowserInfo class instead - * @return - */ - @Deprecated - public static boolean isFF2() { - return BrowserInfo.get().isFF2(); - } - private static final Element escapeHtmlHelper = DOM.createDiv(); /** @@ -249,8 +136,8 @@ public class Util { public static String escapeHTML(String html) { DOM.setInnerText(escapeHtmlHelper, html); String escapedText = DOM.getInnerHTML(escapeHtmlHelper); - if (BrowserInfo.get().isIE() && BrowserInfo.get().getIEVersion() < 9) { - // #7478 IE6-IE8 "incorrectly" returns "<br>" for newlines set using + if (BrowserInfo.get().isIE8()) { + // #7478 IE8 "incorrectly" returns "<br>" for newlines set using // setInnerText. The same for " " which is converted to " " escapedText = escapedText.replaceAll("<(BR|br)>", "\n"); escapedText = escapedText.replaceAll(" ", " "); @@ -275,48 +162,6 @@ public class Util { } /** - * Adds transparent PNG fix to image element; only use for IE6. - * - * @param el - * IMG element - */ - public native static void addPngFix(Element el) - /*-{ - el.attachEvent("onload", $entry(function() { - @com.vaadin.terminal.gwt.client.Util::doIE6PngFix(Lcom/google/gwt/user/client/Element;)(el); - }),false); - }-*/; - - private native static void doPngFix(Element el, String blankImageUrl) - /*-{ - var src = el.src; - if (src.indexOf(".png") < 1) return; - var w = el.width || 16; - var h = el.height || 16; - if(h==30 || w==28) { - setTimeout(function(){ - el.style.height = el.height + "px"; - el.style.width = el.width + "px"; - el.src = blankImageUrl; - },10); - } else { - el.src = blankImageUrl; - el.style.height = h + "px"; - el.style.width = w + "px"; - } - el.style.padding = "0"; - el.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+src+"', sizingMethod='crop')"; - }-*/; - - public static void doIE6PngFix(Element el) { - String blankImageUrl = GWT.getModuleBaseURL() + "ie6pngfix/blank.gif"; - String src = el.getAttribute("src"); - if (src != null && !src.equals(blankImageUrl)) { - doPngFix(el, blankImageUrl); - } - } - - /** * Clones given element as in JavaScript. * * Deprecate this if there appears similar method into GWT someday. @@ -334,11 +179,7 @@ public class Util { public static int measureHorizontalPaddingAndBorder(Element element, int paddingGuess) { String originalWidth = DOM.getStyleAttribute(element, "width"); - String originalOverflow = ""; - if (BrowserInfo.get().isIE6()) { - originalOverflow = DOM.getStyleAttribute(element, "overflow"); - DOM.setStyleAttribute(element, "overflow", "hidden"); - } + int originalOffsetWidth = element.getOffsetWidth(); int widthGuess = (originalOffsetWidth - paddingGuess); if (widthGuess < 1) { @@ -348,9 +189,7 @@ public class Util { int padding = element.getOffsetWidth() - widthGuess; DOM.setStyleAttribute(element, "width", originalWidth); - if (BrowserInfo.get().isIE6()) { - DOM.setStyleAttribute(element, "overflow", originalOverflow); - } + return padding; } @@ -378,23 +217,19 @@ public class Util { int offsetWidth = element.getOffsetWidth(); int offsetHeight = element.getOffsetHeight(); - if (!BrowserInfo.get().isIE7()) { - if (offsetHeight < 1) { - offsetHeight = 1; - } - if (offsetWidth < 1) { - offsetWidth = 10; - } - element.getStyle().setPropertyPx("height", offsetHeight); + if (offsetHeight < 1) { + offsetHeight = 1; + } + if (offsetWidth < 1) { + offsetWidth = 10; } + element.getStyle().setPropertyPx("height", offsetHeight); element.getStyle().setPropertyPx("width", offsetWidth); borders = element.getOffsetWidth() - element.getClientWidth(); element.getStyle().setProperty("width", width); - if (!BrowserInfo.get().isIE7()) { - element.getStyle().setProperty("height", height); - } + element.getStyle().setProperty("height", height); } else { borders = element.getOffsetWidth() - element.getPropertyInt("clientWidth"); @@ -412,7 +247,6 @@ public class Util { int offsetWidth = element.getOffsetWidth(); int offsetHeight = element.getOffsetHeight(); - // if (BrowserInfo.get().isIE6()) { if (offsetHeight < 1) { offsetHeight = 1; } @@ -420,7 +254,6 @@ public class Util { offsetWidth = 10; } element.getStyle().setPropertyPx("width", offsetWidth); - // } element.getStyle().setPropertyPx("height", offsetHeight); @@ -428,9 +261,7 @@ public class Util { - element.getPropertyInt("clientHeight"); element.getStyle().setProperty("height", height); - // if (BrowserInfo.get().isIE6()) { element.getStyle().setProperty("width", width); - // } } else { borders = element.getOffsetHeight() - element.getPropertyInt("clientHeight"); @@ -602,8 +433,7 @@ public class Util { public static void runWebkitOverflowAutoFix(final Element elem) { // Add max version if fix lands sometime to Webkit // Starting from Opera 11.00, also a problem in Opera - if ((BrowserInfo.get().getWebkitVersion() > 0 || BrowserInfo.get() - .getOperaVersion() >= 11) && getNativeScrollbarSize() > 0) { + if (BrowserInfo.get().requiresOverflowAutoFix()) { final String originalOverflow = elem.getStyle().getProperty( "overflow"); if ("hidden".equals(originalOverflow)) { @@ -662,39 +492,28 @@ public class Util { } /** - * Parses the UIDL parameter and fetches the relative size of the component. - * If a dimension is not specified as relative it will return -1. If the - * UIDL does not contain width or height specifications this will return + * Parses shared state and fetches the relative size of the component. If a + * dimension is not specified as relative it will return -1. If the shared + * state does not contain width or height specifications this will return * null. * - * @param uidl + * @param state * @return */ - public static FloatSize parseRelativeSize(UIDL uidl) { - boolean hasAttribute = false; - String w = ""; - String h = ""; - if (uidl.hasAttribute("width")) { - hasAttribute = true; - w = uidl.getStringAttribute("width"); - } - if (uidl.hasAttribute("height")) { - hasAttribute = true; - h = uidl.getStringAttribute("height"); - } - - if (!hasAttribute) { + public static FloatSize parseRelativeSize(ComponentState state) { + if (state.isUndefinedHeight() && state.isUndefinedWidth()) { return null; } - float relativeWidth = Util.parseRelativeSize(w); - float relativeHeight = Util.parseRelativeSize(h); + float relativeWidth = Util.parseRelativeSize(state.getWidth()); + float relativeHeight = Util.parseRelativeSize(state.getHeight()); FloatSize relativeSize = new FloatSize(relativeWidth, relativeHeight); return relativeSize; } + @Deprecated public static boolean isCached(UIDL uidl) { return uidl.getBooleanAttribute("cached"); } @@ -714,27 +533,8 @@ public class Util { } public static void updateRelativeChildrenAndSendSizeUpdateEvent( - ApplicationConnection client, HasWidgets container) { - updateRelativeChildrenAndSendSizeUpdateEvent(client, container, - (Paintable) container); - } - - public static void updateRelativeChildrenAndSendSizeUpdateEvent( - ApplicationConnection client, HasWidgets container, Paintable widget) { - /* - * Relative sized children must be updated first so the component has - * the correct outer dimensions when signaling a size change to the - * parent. - */ - Iterator<Widget> childIterator = container.iterator(); - while (childIterator.hasNext()) { - Widget w = childIterator.next(); - client.handleComponentRelativeSize(w); - } - - HashSet<Paintable> widgets = new HashSet<Paintable>(); - widgets.add(widget); - Util.componentSizeUpdated(widgets); + ApplicationConnection client, HasWidgets container, Widget widget) { + notifyParentOfSizeChange(widget, false); } public static native int getRequiredWidth( @@ -823,92 +623,13 @@ public class Util { }-*/; /** - * IE7 sometimes "forgets" to render content. This function runs a hack to - * workaround the bug if needed. This happens easily in framset. See #3295. - */ - public static void runIE7ZeroSizedBodyFix() { - if (BrowserInfo.get().isIE7()) { - int offsetWidth = RootPanel.getBodyElement().getOffsetWidth(); - if (offsetWidth == 0) { - shakeBodyElement(); - } - } - } - - /** - * Does some very small adjustments to body element. We need this just to - * overcome some IE bugs. - */ - public static void shakeBodyElement() { - final DivElement shaker = Document.get().createDivElement(); - RootPanel.getBodyElement().insertBefore(shaker, - RootPanel.getBodyElement().getFirstChildElement()); - shaker.getStyle().setPropertyPx("height", 0); - shaker.setInnerHTML(" "); - RootPanel.getBodyElement().removeChild(shaker); - - } - - /** - * Locates the child component of <literal>parent</literal> which contains - * the element <literal>element</literal>. The child component is also - * returned if "element" is part of its caption. If - * <literal>element</literal> is not part of any child component, null is - * returned. - * - * This method returns the immediate child of the parent that contains the - * element. See - * {@link #getPaintableForElement(ApplicationConnection, Container, Element)} - * for the deepest nested paintable of parent that contains the element. - * - * @param client - * A reference to ApplicationConnection - * @param parent - * The widget that contains <literal>element</literal>. - * @param element - * An element that is a sub element of the parent - * @return The Paintable which the element is a part of. Null if the element - * does not belong to a child. - */ - public static Paintable getChildPaintableForElement( - ApplicationConnection client, Container parent, Element element) { - Element rootElement = ((Widget) parent).getElement(); - while (element != null && element != rootElement) { - Paintable paintable = client.getPaintable(element); - if (paintable == null) { - String ownerPid = VCaption.getCaptionOwnerPid(element); - if (ownerPid != null) { - paintable = client.getPaintable(ownerPid); - } - } - - if (paintable != null) { - try { - if (parent.hasChildComponent((Widget) paintable)) { - return paintable; - } - } catch (ClassCastException e) { - // We assume everything is a widget however there is no need - // to crash everything if there is a paintable that is not. - } - } - - element = (Element) element.getParentElement(); - } - - return null; - } - - /** * Locates the nested child component of <literal>parent</literal> which * contains the element <literal>element</literal>. The child component is * also returned if "element" is part of its caption. If * <literal>element</literal> is not part of any child component, null is * returned. * - * This method returns the deepest nested Paintable. See - * {@link #getChildPaintableForElement(ApplicationConnection, Container, Element)} - * for the immediate child component of parent that contains the element. + * This method returns the deepest nested VPaintableWidget. * * @param client * A reference to ApplicationConnection @@ -916,18 +637,20 @@ public class Util { * The widget that contains <literal>element</literal>. * @param element * An element that is a sub element of the parent - * @return The Paintable which the element is a part of. Null if the element - * does not belong to a child. + * @return The VPaintableWidget which the element is a part of. Null if the + * element does not belong to a child. */ - public static Paintable getPaintableForElement( + public static ComponentConnector getConnectorForElement( ApplicationConnection client, Widget parent, Element element) { Element rootElement = parent.getElement(); while (element != null && element != rootElement) { - Paintable paintable = client.getPaintable(element); + ComponentConnector paintable = ConnectorMap.get(client) + .getConnector(element); if (paintable == null) { String ownerPid = VCaption.getCaptionOwnerPid(element); if (ownerPid != null) { - paintable = client.getPaintable(ownerPid); + paintable = (ComponentConnector) ConnectorMap.get(client) + .getConnector(ownerPid); } } @@ -965,6 +688,24 @@ public class Util { }-*/; /** + * Helper method to find the nearest parent paintable instance by traversing + * the DOM upwards from given element. + * + * @param element + * the element to start from + */ + public static ComponentConnector findPaintable( + ApplicationConnection client, Element element) { + Widget widget = Util.findWidget(element, null); + ConnectorMap vPaintableMap = ConnectorMap.get(client); + while (widget != null && !vPaintableMap.isConnector(widget)) { + widget = widget.getParent(); + } + return vPaintableMap.getConnector(widget); + + } + + /** * Helper method to find first instance of given Widget type found by * traversing DOM upwards from given element. * @@ -1071,14 +812,34 @@ public class Util { return idx; } - private static void printPaintablesVariables(ArrayList<String[]> vars, - String id, ApplicationConnection c) { - Paintable paintable = c.getPaintable(id); + private static void printPaintablesInvocations( + ArrayList<MethodInvocation> invocations, String id, + ApplicationConnection c) { + ComponentConnector paintable = (ComponentConnector) ConnectorMap.get(c) + .getConnector(id); if (paintable != null) { VConsole.log("\t" + id + " (" + paintable.getClass() + ") :"); - for (String[] var : vars) { - VConsole.log("\t\t" + var[1] + " (" + var[2] + ")" + " : " - + var[0]); + for (MethodInvocation invocation : invocations) { + Object[] parameters = invocation.getParameters(); + String formattedParams = null; + if (ApplicationConnection.UPDATE_VARIABLE_METHOD + .equals(invocation.getMethodName()) + && parameters.length == 2) { + // name, value + Object value = parameters[1]; + // TODO paintables inside lists/maps get rendered as + // components in the debug console + String formattedValue = value instanceof ServerConnector ? ((ServerConnector) value) + .getConnectorId() : String.valueOf(value); + formattedParams = parameters[0] + " : " + formattedValue; + } + if (null == formattedParams) { + formattedParams = (null != parameters) ? Arrays + .toString(parameters) : null; + } + VConsole.log("\t\t" + invocation.getInterfaceName() + "." + + invocation.getMethodName() + "(" + formattedParams + + ")"); } } else { VConsole.log("\t" + id + ": Warning: no corresponding paintable!"); @@ -1086,31 +847,25 @@ public class Util { } static void logVariableBurst(ApplicationConnection c, - ArrayList<String> loggedBurst) { + ArrayList<MethodInvocation> loggedBurst) { try { VConsole.log("Variable burst to be sent to server:"); String curId = null; - ArrayList<String[]> vars = new ArrayList<String[]>(); + ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>(); for (int i = 0; i < loggedBurst.size(); i++) { - String value = loggedBurst.get(i++); - String[] split = loggedBurst - .get(i) - .split(String - .valueOf(ApplicationConnection.VAR_FIELD_SEPARATOR)); - String id = split[0]; + String id = loggedBurst.get(i).getConnectorId(); if (curId == null) { curId = id; } else if (!curId.equals(id)) { - printPaintablesVariables(vars, curId, c); - vars.clear(); + printPaintablesInvocations(invocations, curId, c); + invocations.clear(); curId = id; } - split[0] = value; - vars.add(split); + invocations.add(loggedBurst.get(i)); } - if (!vars.isEmpty()) { - printPaintablesVariables(vars, curId, c); + if (!invocations.isEmpty()) { + printPaintablesInvocations(invocations, curId, c); } } catch (Exception e) { VConsole.error(e); @@ -1334,4 +1089,49 @@ public class Util { boolean touchEvent = Util.isTouchEvent(event); return touchEvent || event.getButton() == Event.BUTTON_LEFT; } + + /** + * Performs a shallow comparison of the collections. + * + * @param collection1 + * The first collection + * @param collection2 + * The second collection + * @return true if the collections contain the same elements in the same + * order, false otherwise + */ + public static boolean collectionsEquals(Collection collection1, + Collection collection2) { + if (collection1 == null) { + return collection2 == null; + } + if (collection2 == null) { + return false; + } + Iterator<Object> collection1Iterator = collection1.iterator(); + Iterator<Object> collection2Iterator = collection2.iterator(); + + while (collection1Iterator.hasNext()) { + if (!collection2Iterator.hasNext()) { + return false; + } + Object collection1Object = collection1Iterator.next(); + Object collection2Object = collection2Iterator.next(); + if (collection1Object != collection2Object) { + return false; + } + } + if (collection2Iterator.hasNext()) { + return false; + } + + return true; + } + + public static String getConnectorString(ServerConnector p) { + if (p == null) { + return "null"; + } + return getSimpleName(p) + " (" + p.getConnectorId() + ")"; + } } diff --git a/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java b/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java index aaef981bab..89e106f063 100644 --- a/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java +++ b/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java @@ -22,6 +22,9 @@ public class VBrowserDetails implements Serializable { private boolean isWebKit = false; private boolean isPresto = false; + private boolean isChromeFrameCapable = false; + private boolean isChromeFrame = false; + private boolean isSafari = false; private boolean isChrome = false; private boolean isFirefox = false; @@ -59,6 +62,10 @@ public class VBrowserDetails implements Serializable { && (userAgent.indexOf("webtv") == -1); isFirefox = userAgent.indexOf(" firefox/") != -1; + // chromeframe + isChromeFrameCapable = userAgent.indexOf("chromeframe") != -1; + isChromeFrame = isChromeFrameCapable && !isChrome; + // Rendering engine version try { if (isGecko) { @@ -210,6 +217,24 @@ public class VBrowserDetails implements Serializable { } /** + * Tests if the browser is capable of running ChromeFrame. + * + * @return true if it has ChromeFrame, false otherwise + */ + public boolean isChromeFrameCapable() { + return isChromeFrameCapable; + } + + /** + * Tests if the browser is running ChromeFrame. + * + * @return true if it is ChromeFrame, false otherwise + */ + public boolean isChromeFrame() { + return isChromeFrame; + } + + /** * Tests if the browser is Opera. * * @return true if it is Opera, false otherwise @@ -302,4 +327,30 @@ public class VBrowserDetails implements Serializable { return isLinux; } + /** + * Checks if the browser is so old that it simply won't work with a Vaadin + * application. NOTE that the browser might still be capable of running + * Crome Frame, so you might still want to check + * {@link #isChromeFrameCapable()} if this returns true. + * + * @return true if the browser won't work, false if not the browser is + * supported or might work + */ + public boolean isTooOldToFunctionProperly() { + if (isIE() && getBrowserMajorVersion() < 8) { + return true; + } + if (isSafari() && getBrowserMajorVersion() < 5) { + return true; + } + if (isFirefox() && getBrowserMajorVersion() < 4) { + return true; + } + if (isOpera() && getBrowserMajorVersion() < 11) { + return true; + } + + return false; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/VCaption.java b/src/com/vaadin/terminal/gwt/client/VCaption.java index c4b61d2544..6f3fcf2c3a 100644 --- a/src/com/vaadin/terminal/gwt/client/VCaption.java +++ b/src/com/vaadin/terminal/gwt/client/VCaption.java @@ -8,13 +8,14 @@ import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.HTML; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; import com.vaadin.terminal.gwt.client.ui.Icon; public class VCaption extends HTML { public static final String CLASSNAME = "v-caption"; - private final Paintable owner; + private final ComponentConnector owner; private Element errorIndicatorElement; @@ -24,38 +25,52 @@ public class VCaption extends HTML { private Element captionText; - private Element clearElement; - private final ApplicationConnection client; private boolean placedAfterComponent = false; - private boolean iconOnloadHandled = false; private int maxWidth = -1; - protected static final String ATTRIBUTE_ICON = "icon"; - protected static final String ATTRIBUTE_CAPTION = "caption"; - protected static final String ATTRIBUTE_DESCRIPTION = "description"; - protected static final String ATTRIBUTE_REQUIRED = "required"; - protected static final String ATTRIBUTE_ERROR = "error"; - protected static final String ATTRIBUTE_HIDEERRORS = "hideErrors"; + private enum InsertPosition { + ICON, CAPTION, REQUIRED, ERROR + } - private static final String CLASSNAME_CLEAR = CLASSNAME + "-clearelem"; + /** + * Creates a caption that is not linked to a {@link ComponentConnector}. + * + * When using this constructor, {@link #getOwner()} returns null. + * + * @param client + * ApplicationConnection + * @deprecated all captions should be associated with a paintable widget and + * be updated from shared state, not UIDL + */ + @Deprecated + public VCaption(ApplicationConnection client) { + super(); + this.client = client; + owner = null; + + setStyleName(CLASSNAME); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + + } /** + * Creates a caption for a {@link ComponentConnector}. * * @param component - * optional owner of caption. If not set, getOwner will return - * null + * owner of caption, not null * @param client + * ApplicationConnection */ - public VCaption(Paintable component, ApplicationConnection client) { + public VCaption(ComponentConnector component, ApplicationConnection client) { super(); this.client = client; owner = component; if (client != null && owner != null) { - setOwnerPid(getElement(), client.getPid(owner)); + setOwnerPid(getElement(), owner.getConnectorId()); } setStyleName(CLASSNAME); @@ -66,13 +81,13 @@ public class VCaption extends HTML { /** * Updates the caption from UIDL. * - * @param uidl + * This method may only be called when the caption has an owner - otherwise, + * use {@link #updateCaptionWithoutOwner(UIDL, String, boolean, boolean)}. + * * @return true if the position where the caption should be placed has * changed */ - public boolean updateCaption(UIDL uidl) { - setVisible(!uidl.getBooleanAttribute("invisible")); - + public boolean updateCaption() { boolean wasPlacedAfterComponent = placedAfterComponent; // Caption is placed after component unless there is some part which @@ -80,25 +95,27 @@ public class VCaption extends HTML { placedAfterComponent = true; String style = CLASSNAME; - if (uidl.hasAttribute("style")) { - final String[] styles = uidl.getStringAttribute("style").split(" "); - for (int i = 0; i < styles.length; i++) { - style += " " + CLASSNAME + "-" + styles[i]; + if (owner.getState().hasStyles()) { + for (String customStyle : owner.getState().getStyles()) { + style += " " + CLASSNAME + "-" + customStyle; } } - - if (uidl.hasAttribute("disabled")) { + if (!owner.isEnabled()) { style += " " + ApplicationConnection.DISABLED_CLASSNAME; } - setStyleName(style); - boolean hasIcon = uidl.hasAttribute(ATTRIBUTE_ICON); - boolean hasText = uidl.hasAttribute(ATTRIBUTE_CAPTION); - boolean hasDescription = uidl.hasAttribute(ATTRIBUTE_DESCRIPTION); - boolean showRequired = uidl.getBooleanAttribute(ATTRIBUTE_REQUIRED); - boolean showError = uidl.hasAttribute(ATTRIBUTE_ERROR) - && !uidl.getBooleanAttribute(ATTRIBUTE_HIDEERRORS); + boolean hasIcon = owner.getState().getIcon() != null; + boolean showRequired = false; + boolean showError = owner.getState().getErrorMessage() != null; + if (owner.getState() instanceof AbstractFieldState) { + AbstractFieldState abstractFieldState = (AbstractFieldState) owner + .getState(); + showError = showError && !abstractFieldState.isHideErrors(); + } + if (owner instanceof AbstractFieldConnector) { + showRequired = ((AbstractFieldConnector) owner).isRequired(); + } if (hasIcon) { if (icon == null) { @@ -107,13 +124,12 @@ public class VCaption extends HTML { icon.setHeight("0"); DOM.insertChild(getElement(), icon.getElement(), - getInsertPosition(ATTRIBUTE_ICON)); + getInsertPosition(InsertPosition.ICON)); } // Icon forces the caption to be above the component placedAfterComponent = false; - iconOnloadHandled = false; - icon.setUri(uidl.getStringAttribute(ATTRIBUTE_ICON)); + icon.setUri(owner.getState().getIcon().getURL()); } else if (icon != null) { // Remove existing @@ -121,7 +137,7 @@ public class VCaption extends HTML { icon = null; } - if (hasText) { + if (owner.getState().getCaption() != null) { // A caption text should be shown if the attribute is set // If the caption is null the ATTRIBUTE_CAPTION should not be set to // avoid ending up here. @@ -131,11 +147,11 @@ public class VCaption extends HTML { captionText.setClassName("v-captiontext"); DOM.insertChild(getElement(), captionText, - getInsertPosition(ATTRIBUTE_CAPTION)); + getInsertPosition(InsertPosition.CAPTION)); } // Update caption text - String c = uidl.getStringAttribute(ATTRIBUTE_CAPTION); + String c = owner.getState().getCaption(); // A text forces the caption to be above the component. placedAfterComponent = false; if (c == null || c.trim().equals("")) { @@ -158,12 +174,10 @@ public class VCaption extends HTML { captionText = null; } - if (hasDescription) { - if (captionText != null) { - addStyleDependentName("hasdescription"); - } else { - removeStyleDependentName("hasdescription"); - } + if (owner.getState().hasDescription() && captionText != null) { + addStyleDependentName("hasdescription"); + } else { + removeStyleDependentName("hasdescription"); } if (showRequired) { @@ -174,7 +188,7 @@ public class VCaption extends HTML { DOM.setInnerText(requiredFieldIndicator, "*"); DOM.insertChild(getElement(), requiredFieldIndicator, - getInsertPosition(ATTRIBUTE_REQUIRED)); + getInsertPosition(InsertPosition.REQUIRED)); } } else if (requiredFieldIndicator != null) { // Remove existing @@ -190,7 +204,7 @@ public class VCaption extends HTML { "v-errorindicator"); DOM.insertChild(getElement(), errorIndicatorElement, - getInsertPosition(ATTRIBUTE_ERROR)); + getInsertPosition(InsertPosition.ERROR)); } } else if (errorIndicatorElement != null) { // Remove existing @@ -198,25 +212,19 @@ public class VCaption extends HTML { errorIndicatorElement = null; } - if (clearElement == null) { - clearElement = DOM.createDiv(); - clearElement.setClassName(CLASSNAME_CLEAR); - getElement().appendChild(clearElement); - } - return (wasPlacedAfterComponent != placedAfterComponent); } - private int getInsertPosition(String element) { + private int getInsertPosition(InsertPosition element) { int pos = 0; - if (element.equals(ATTRIBUTE_ICON)) { + if (InsertPosition.ICON.equals(element)) { return pos; } if (icon != null) { pos++; } - if (element.equals(ATTRIBUTE_CAPTION)) { + if (InsertPosition.CAPTION.equals(element)) { return pos; } @@ -224,19 +232,115 @@ public class VCaption extends HTML { pos++; } - if (element.equals(ATTRIBUTE_REQUIRED)) { + if (InsertPosition.REQUIRED.equals(element)) { return pos; } if (requiredFieldIndicator != null) { pos++; } - // if (element.equals(ATTRIBUTE_ERROR)) { + // if (InsertPosition.ERROR.equals(element)) { // } return pos; } + @Deprecated + public boolean updateCaptionWithoutOwner(String caption, boolean disabled, + boolean hasDescription, boolean hasError, String iconURL) { + boolean wasPlacedAfterComponent = placedAfterComponent; + + // Caption is placed after component unless there is some part which + // moves it above. + placedAfterComponent = true; + + String style = VCaption.CLASSNAME; + if (disabled) { + style += " " + ApplicationConnection.DISABLED_CLASSNAME; + } + setStyleName(style); + if (hasDescription) { + if (captionText != null) { + addStyleDependentName("hasdescription"); + } else { + removeStyleDependentName("hasdescription"); + } + } + boolean hasIcon = iconURL != null; + + if (hasIcon) { + if (icon == null) { + icon = new Icon(client); + icon.setWidth("0"); + icon.setHeight("0"); + + DOM.insertChild(getElement(), icon.getElement(), + getInsertPosition(InsertPosition.ICON)); + } + // Icon forces the caption to be above the component + placedAfterComponent = false; + + icon.setUri(iconURL); + + } else if (icon != null) { + // Remove existing + DOM.removeChild(getElement(), icon.getElement()); + icon = null; + } + + if (caption != null) { + // A caption text should be shown if the attribute is set + // If the caption is null the ATTRIBUTE_CAPTION should not be set to + // avoid ending up here. + + if (captionText == null) { + captionText = DOM.createDiv(); + captionText.setClassName("v-captiontext"); + + DOM.insertChild(getElement(), captionText, + getInsertPosition(InsertPosition.CAPTION)); + } + + // Update caption text + // A text forces the caption to be above the component. + placedAfterComponent = false; + if (caption.trim().equals("")) { + // This is required to ensure that the caption uses space in all + // browsers when it is set to the empty string. If there is an + // icon, error indicator or required indicator they will ensure + // that space is reserved. + if (!hasIcon && !hasError) { + captionText.setInnerHTML(" "); + } + } else { + DOM.setInnerText(captionText, caption); + } + + } else if (captionText != null) { + // Remove existing + DOM.removeChild(getElement(), captionText); + captionText = null; + } + + if (hasError) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setInnerHTML(errorIndicatorElement, " "); + DOM.setElementProperty(errorIndicatorElement, "className", + "v-errorindicator"); + + DOM.insertChild(getElement(), errorIndicatorElement, + getInsertPosition(InsertPosition.ERROR)); + } + } else if (errorIndicatorElement != null) { + // Remove existing + getElement().removeChild(errorIndicatorElement); + errorIndicatorElement = null; + } + + return (wasPlacedAfterComponent != placedAfterComponent); + } + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -246,16 +350,10 @@ public class VCaption extends HTML { } if (DOM.eventGetType(event) == Event.ONLOAD - && icon.getElement() == target && !iconOnloadHandled) { + && icon.getElement() == target) { icon.setWidth(""); icon.setHeight(""); - /* - * IE6 pngFix causes two onload events to be fired and we want to - * react only to the first one - */ - iconOnloadHandled = true; - // if max width defined, recalculate if (maxWidth != -1) { setMaxWidth(maxWidth); @@ -272,24 +370,21 @@ public class VCaption extends HTML { * the responsibility of reacting to ONLOAD from VCaption to layouts */ if (owner != null) { - Util.notifyParentOfSizeChange(owner, true); + Util.notifyParentOfSizeChange(owner.getWidget(), true); } else { VConsole.log("Warning: Icon load event was not propagated because VCaption owner is unknown."); } } } - public static boolean isNeeded(UIDL uidl) { - if (uidl.getStringAttribute(ATTRIBUTE_CAPTION) != null) { - return true; - } - if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { + public static boolean isNeeded(ComponentState state) { + if (state.getCaption() != null) { return true; } - if (uidl.hasAttribute(ATTRIBUTE_ICON)) { + if (state.getIcon() != null) { return true; } - if (uidl.hasAttribute(ATTRIBUTE_REQUIRED)) { + if (state.getErrorMessage() != null) { return true; } @@ -301,7 +396,7 @@ public class VCaption extends HTML { * * @return owner Widget */ - public Paintable getOwner() { + public ComponentConnector getOwner() { return owner; } diff --git a/src/com/vaadin/terminal/gwt/client/VCaptionWrapper.java b/src/com/vaadin/terminal/gwt/client/VCaptionWrapper.java index dbecf96dd0..a8dabb8652 100644 --- a/src/com/vaadin/terminal/gwt/client/VCaptionWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/VCaptionWrapper.java @@ -5,28 +5,35 @@ package com.vaadin.terminal.gwt.client; import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Widget; public class VCaptionWrapper extends FlowPanel { public static final String CLASSNAME = "v-captionwrapper"; VCaption caption; - Paintable widget; + ComponentConnector wrappedConnector; - public VCaptionWrapper(Paintable toBeWrapped, ApplicationConnection client) { + /** + * Creates a new caption wrapper panel. + * + * @param toBeWrapped + * paintable that the caption is associated with, not null + * @param client + * ApplicationConnection + */ + public VCaptionWrapper(ComponentConnector toBeWrapped, + ApplicationConnection client) { caption = new VCaption(toBeWrapped, client); add(caption); - widget = toBeWrapped; - add((Widget) widget); + wrappedConnector = toBeWrapped; + add(wrappedConnector.getWidget()); setStyleName(CLASSNAME); } - public void updateCaption(UIDL uidl) { - caption.updateCaption(uidl); - setVisible(!uidl.getBooleanAttribute("invisible")); + public void updateCaption() { + caption.updateCaption(); } - public Paintable getPaintable() { - return widget; + public ComponentConnector getWrappedConnector() { + return wrappedConnector; } } diff --git a/src/com/vaadin/terminal/gwt/client/VConsole.java b/src/com/vaadin/terminal/gwt/client/VConsole.java index a01fa16558..dee8529a84 100644 --- a/src/com/vaadin/terminal/gwt/client/VConsole.java +++ b/src/com/vaadin/terminal/gwt/client/VConsole.java @@ -82,8 +82,8 @@ public class VConsole { public static void printLayoutProblems(ValueMap meta, ApplicationConnection applicationConnection, - Set<Paintable> zeroHeightComponents, - Set<Paintable> zeroWidthComponents) { + Set<ComponentConnector> zeroHeightComponents, + Set<ComponentConnector> zeroWidthComponents) { impl.printLayoutProblems(meta, applicationConnection, zeroHeightComponents, zeroWidthComponents); } diff --git a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java index 19b478b098..5eaf78f255 100644 --- a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java +++ b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java @@ -4,6 +4,9 @@ package com.vaadin.terminal.gwt.client; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -19,6 +22,8 @@ import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.MouseOutEvent; +import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.event.shared.UmbrellaException; import com.google.gwt.http.client.Request; @@ -27,6 +32,7 @@ import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response; import com.google.gwt.http.client.UrlBuilder; +import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; @@ -48,6 +54,9 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; +import com.vaadin.terminal.gwt.client.ui.root.RootConnector; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; /** * A helper console for client side development. The debug console can also be @@ -90,17 +99,17 @@ public class VDebugConsole extends VOverlay implements Console { for (ApplicationConnection a : ApplicationConfiguration .getRunningApplications()) { - Paintable paintable = Util.getPaintableForElement(a, - a.getView(), eventTarget); - if (paintable == null) { - paintable = Util.getPaintableForElement(a, + ComponentConnector connector = Util.getConnectorForElement( + a, a.getRootConnector().getWidget(), eventTarget); + if (connector == null) { + connector = Util.getConnectorForElement(a, RootPanel.get(), eventTarget); } - if (paintable != null) { - String pid = a.getPid(paintable); - VUIDLBrowser.highlight(paintable); + if (connector != null) { + String pid = connector.getConnectorId(); + VUIDLBrowser.highlight(connector); label.setText("Currently focused :" - + paintable.getClass() + " ID:" + pid); + + connector.getClass() + " ID:" + pid); event.cancel(); event.consume(); event.getNativeEvent().stopPropagation(); @@ -119,10 +128,10 @@ public class VDebugConsole extends VOverlay implements Console { .getClientY()); for (ApplicationConnection a : ApplicationConfiguration .getRunningApplications()) { - Paintable paintable = Util.getPaintableForElement(a, - a.getView(), eventTarget); + ComponentConnector paintable = Util.getConnectorForElement( + a, a.getRootConnector().getWidget(), eventTarget); if (paintable == null) { - paintable = Util.getPaintableForElement(a, + paintable = Util.getConnectorForElement(a, RootPanel.get(), eventTarget); } @@ -150,6 +159,7 @@ public class VDebugConsole extends VOverlay implements Console { private Button analyzeLayout = new Button("AL"); private Button savePosition = new Button("S"); private Button highlight = new Button("H"); + private Button connectorStats = new Button("CS"); private CheckBox hostedMode = new CheckBox("GWT"); private CheckBox autoScroll = new CheckBox("Autoscroll "); private HorizontalPanel actions; @@ -336,11 +346,13 @@ public class VDebugConsole extends VOverlay implements Console { if (msg == null) { msg = "null"; } + msg = addTimestamp(msg); // remoteLog(msg); logToDebugWindow(msg, false); GWT.log(msg); consoleLog(msg); + System.out.println(msg); } private List<String> msgQueue = new LinkedList<String>(); @@ -426,11 +438,22 @@ public class VDebugConsole extends VOverlay implements Console { if (msg == null) { msg = "null"; } - + msg = addTimestamp(msg); logToDebugWindow(msg, true); GWT.log(msg); consoleErr(msg); + System.out.println(msg); + + } + + DateTimeFormat timestampFormat = DateTimeFormat.getFormat("HH:mm:ss:SSS"); + + @SuppressWarnings("deprecation") + private String addTimestamp(String msg) { + Date date = new Date(); + String timestamp = timestampFormat.format(date); + return timestamp + " " + msg; } /* @@ -496,8 +519,8 @@ public class VDebugConsole extends VOverlay implements Console { }-*/; public void printLayoutProblems(ValueMap meta, ApplicationConnection ac, - Set<Paintable> zeroHeightComponents, - Set<Paintable> zeroWidthComponents) { + Set<ComponentConnector> zeroHeightComponents, + Set<ComponentConnector> zeroWidthComponents) { JsArray<ValueMap> valueMapArray = meta .getJSValueMapArray("invalidLayouts"); int size = valueMapArray.length(); @@ -534,9 +557,10 @@ public class VDebugConsole extends VOverlay implements Console { } private void printClientSideDetectedIssues( - Set<Paintable> zeroHeightComponents, ApplicationConnection ac) { - for (final Paintable paintable : zeroHeightComponents) { - final Container layout = Util.getLayout((Widget) paintable); + Set<ComponentConnector> zeroHeightComponents, + ApplicationConnection ac) { + for (final ComponentConnector paintable : zeroHeightComponents) { + final Widget layout = paintable.getParent().getWidget(); VerticalPanel errorDetails = new VerticalPanel(); errorDetails.add(new Label("" + Util.getSimpleName(paintable) @@ -546,7 +570,7 @@ public class VDebugConsole extends VOverlay implements Console { emphasisInUi.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { if (paintable != null) { - Element element2 = ((Widget) layout).getElement(); + Element element2 = layout.getElement(); Widget.setStyleName(element2, "invalidlayout", emphasisInUi.getValue()); } @@ -560,7 +584,8 @@ public class VDebugConsole extends VOverlay implements Console { private void printLayoutError(ValueMap valueMap, SimpleTree root, final ApplicationConnection ac) { final String pid = valueMap.getString("id"); - final Paintable paintable = ac.getPaintable(pid); + final ComponentConnector paintable = (ComponentConnector) ConnectorMap + .get(ac).getConnector(pid); SimpleTree errorNode = new SimpleTree(); VerticalPanel errorDetails = new VerticalPanel(); @@ -578,7 +603,7 @@ public class VDebugConsole extends VOverlay implements Console { emphasisInUi.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { if (paintable != null) { - Element element2 = ((Widget) paintable).getElement(); + Element element2 = paintable.getWidget().getElement(); Widget.setStyleName(element2, "invalidlayout", emphasisInUi.getValue()); } @@ -614,15 +639,34 @@ public class VDebugConsole extends VOverlay implements Console { } public void error(Throwable e) { + handleError(e, this); + } + + static void handleError(Throwable e, Console target) { if (e instanceof UmbrellaException) { UmbrellaException ue = (UmbrellaException) e; for (Throwable t : ue.getCauses()) { - error(t); + target.error(t); } return; } - error(Util.getSimpleName(e) + ": " + e.getMessage()); + String exceptionText = Util.getSimpleName(e); + String message = e.getMessage(); + if (message != null && message.length() != 0) { + exceptionText += ": " + e.getMessage(); + } + target.error(exceptionText); GWT.log(e.getMessage(), e); + if (!GWT.isProdMode()) { + e.printStackTrace(); + } + try { + VNotification.createNotification(VNotification.DELAY_FOREVER).show( + "<h1>Uncaught client side exception</h1><br />" + + exceptionText, VNotification.CENTERED, "error"); + } catch (Exception e2) { + // Just swallow this exception + } } public void init() { @@ -661,6 +705,8 @@ public class VDebugConsole extends VOverlay implements Console { actions.add(forceLayout); actions.add(analyzeLayout); actions.add(highlight); + actions.add(connectorStats); + connectorStats.setTitle("Show connector statistics for client"); highlight .setTitle("Select a component and print details about it to the server log and client side console."); actions.add(savePosition); @@ -788,6 +834,15 @@ public class VDebugConsole extends VOverlay implements Console { }); } + connectorStats.addClickHandler(new ClickHandler() { + + public void onClick(ClickEvent event) { + for (ApplicationConnection a : ApplicationConfiguration + .getRunningApplications()) { + dumpConnectorInfo(a); + } + } + }); log("Starting Vaadin client side engine. Widgetset: " + GWT.getModuleName()); @@ -802,6 +857,93 @@ public class VDebugConsole extends VOverlay implements Console { } + protected void dumpConnectorInfo(ApplicationConnection a) { + RootConnector root = a.getRootConnector(); + log("================"); + log("Connector hierarchy for Root: " + root.getState().getCaption() + + " (" + root.getConnectorId() + ")"); + Set<ComponentConnector> connectorsInHierarchy = new HashSet<ComponentConnector>(); + SimpleTree rootHierachy = dumpConnectorHierarchy(root, "", + connectorsInHierarchy); + if (panel.isAttached()) { + rootHierachy.open(true); + panel.add(rootHierachy); + } + + ConnectorMap connectorMap = a.getConnectorMap(); + Collection<? extends ServerConnector> registeredConnectors = connectorMap + .getConnectors(); + log("Sub windows:"); + Set<ComponentConnector> subWindowHierarchyConnectors = new HashSet<ComponentConnector>(); + for (WindowConnector wc : root.getSubWindows()) { + SimpleTree windowHierachy = dumpConnectorHierarchy(wc, "", + subWindowHierarchyConnectors); + if (panel.isAttached()) { + windowHierachy.open(true); + panel.add(windowHierachy); + } + } + log("Registered connectors not in hierarchy (should be empty):"); + for (ServerConnector registeredConnector : registeredConnectors) { + + if (connectorsInHierarchy.contains(registeredConnector)) { + continue; + } + + if (subWindowHierarchyConnectors.contains(registeredConnector)) { + continue; + } + error(getConnectorString(registeredConnector)); + + } + log("Unregistered connectors in hierarchy (should be empty):"); + for (ServerConnector hierarchyConnector : connectorsInHierarchy) { + if (!connectorMap.hasConnector(hierarchyConnector.getConnectorId())) { + error(getConnectorString(hierarchyConnector)); + } + + } + + log("================"); + + } + + private SimpleTree dumpConnectorHierarchy( + final ComponentConnector connector, String indent, + Set<ComponentConnector> connectors) { + SimpleTree simpleTree = new SimpleTree(getConnectorString(connector)) { + @Override + protected void select(ClickEvent event) { + super.select(event); + VUIDLBrowser.highlight(connector); + } + }; + simpleTree.addDomHandler(new MouseOutHandler() { + public void onMouseOut(MouseOutEvent event) { + VUIDLBrowser.deHiglight(); + } + }, MouseOutEvent.getType()); + connectors.add(connector); + + String msg = indent + "* " + getConnectorString(connector); + GWT.log(msg); + consoleLog(msg); + System.out.println(msg); + + if (connector instanceof ComponentContainerConnector) { + for (ComponentConnector c : ((ComponentContainerConnector) connector) + .getChildren()) { + simpleTree.add(dumpConnectorHierarchy(c, indent + " ", + connectors)); + } + } + return simpleTree; + } + + private static String getConnectorString(ServerConnector connector) { + return Util.getConnectorString(connector); + } + public void setQuietMode(boolean quietDebugMode) { quietMode = quietDebugMode; } diff --git a/src/com/vaadin/terminal/gwt/client/VErrorMessage.java b/src/com/vaadin/terminal/gwt/client/VErrorMessage.java index 58bdccaf55..add6ee4780 100644 --- a/src/com/vaadin/terminal/gwt/client/VErrorMessage.java +++ b/src/com/vaadin/terminal/gwt/client/VErrorMessage.java @@ -4,8 +4,6 @@ package com.vaadin.terminal.gwt.client; -import java.util.Iterator; - import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.FlowPanel; @@ -20,30 +18,13 @@ public class VErrorMessage extends FlowPanel { setStyleName(CLASSNAME); } - public void updateFromUIDL(UIDL uidl) { + public void updateMessage(String htmlErrorMessage) { clear(); - if (uidl.getChildCount() == 0) { + if (htmlErrorMessage == null || htmlErrorMessage.length() == 0) { add(new HTML(" ")); } else { - for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) { - final Object child = it.next(); - if (child instanceof String) { - final String errorMessage = (String) child; - add(new HTML(errorMessage)); - } else { - try { - final VErrorMessage childError = new VErrorMessage(); - childError.updateFromUIDL((UIDL) child); - add(childError); - } catch (Exception e) { - // TODO XML type error, check if this can even happen - // anymore?? - final UIDL.XML xml = (UIDL.XML) child; - add(new HTML(xml.getXMLAsString())); - - } - } - } + // pre-formatted on the server as div per child + add(new HTML(htmlErrorMessage)); } } diff --git a/src/com/vaadin/terminal/gwt/client/VTooltip.java b/src/com/vaadin/terminal/gwt/client/VTooltip.java index 974e2f1f32..70f4a0de0a 100644 --- a/src/com/vaadin/terminal/gwt/client/VTooltip.java +++ b/src/com/vaadin/terminal/gwt/client/VTooltip.java @@ -27,7 +27,7 @@ public class VTooltip extends VOverlay { private static final int QUICK_OPEN_DELAY = 100; VErrorMessage em = new VErrorMessage(); Element description = DOM.createDiv(); - private Paintable tooltipOwner; + private ComponentConnector tooltipOwner; private boolean closing = false; private boolean opening = false; @@ -56,9 +56,9 @@ public class VTooltip extends VOverlay { */ private void show(TooltipInfo info) { boolean hasContent = false; - if (info.getErrorUidl() != null) { + if (info.getErrorMessage() != null) { em.setVisible(true); - em.updateFromUIDL(info.getErrorUidl()); + em.updateMessage(info.getErrorMessage()); hasContent = true; } else { em.setVisible(false); @@ -115,7 +115,7 @@ public class VTooltip extends VOverlay { } } - public void showTooltip(Paintable owner, Event event, Object key) { + public void showTooltip(ComponentConnector owner, Event event, Object key) { if (closing && tooltipOwner == owner && tooltipKey == key) { // return to same tooltip, cancel closing closeTimer.cancel(); @@ -212,7 +212,8 @@ public class VTooltip extends VOverlay { } - public void handleTooltipEvent(Event event, Paintable owner, Object key) { + public void handleTooltipEvent(Event event, ComponentConnector owner, + Object key) { final int type = DOM.eventGetType(event); if ((VTooltip.TOOLTIP_EVENTS & type) == type) { if (type == Event.ONMOUSEOVER) { diff --git a/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java b/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java index 95d2fd0b5f..9fa973dc29 100644 --- a/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java +++ b/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java @@ -24,9 +24,8 @@ import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ui.VUnknownComponent; -import com.vaadin.terminal.gwt.client.ui.VView; -import com.vaadin.terminal.gwt.client.ui.VWindow; +import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; +import com.vaadin.terminal.gwt.client.ui.window.VWindow; public class VUIDLBrowser extends SimpleTree { private static final String HELP = "Shift click handle to open recursively. Click components to hightlight them on client side. Shift click components to highlight them also on the server side."; @@ -47,7 +46,9 @@ public class VUIDLBrowser extends SimpleTree { } Set<String> keySet = u.getKeySet(); for (String key : keySet) { - if (key.equals("changes")) { + if (key.equals("state")) { + // TODO print updated shared states + } else if (key.equals("changes")) { JsArray<UIDL> jsValueMapArray = u.getJSValueMapArray("changes") .cast(); for (int i = 0; i < jsValueMapArray.length(); i++) { @@ -77,8 +78,7 @@ public class VUIDLBrowser extends SimpleTree { try { String name = uidl.getTag(); try { - Integer.parseInt(name); - name = getNodeName(uidl, conf, name); + name = getNodeName(uidl, conf, Integer.parseInt(name)); } catch (Exception e) { // NOP } @@ -97,15 +97,12 @@ public class VUIDLBrowser extends SimpleTree { } private String getNodeName(UIDL uidl, ApplicationConfiguration conf, - String name) { - Class<? extends Paintable> widgetClassByDecodedTag = conf - .getWidgetClassByEncodedTag(name); - if (widgetClassByDecodedTag == VUnknownComponent.class) { - return conf.getUnknownServerClassNameByEncodedTagName(name) + int tag) { + Class<? extends ComponentConnector> widgetClassByDecodedTag = conf + .getWidgetClassByEncodedTag(tag); + if (widgetClassByDecodedTag == UnknownComponentConnector.class) { + return conf.getUnknownServerClassNameByTag(tag) + "(NO CLIENT IMPLEMENTATION FOUND)"; - } else if (widgetClassByDecodedTag == VView.class - && uidl.hasAttribute("sub")) { - return "com.vaadin.terminal.gwt.ui.VWindow"; } else { return widgetClassByDecodedTag.getName(); } @@ -130,8 +127,8 @@ public class VUIDLBrowser extends SimpleTree { // same // host page for (ApplicationConnection applicationConnection : runningApplications) { - Paintable paintable = applicationConnection.getPaintable(uidl - .getId()); + ComponentConnector paintable = (ComponentConnector) ConnectorMap + .get(applicationConnection).getConnector(uidl.getId()); highlight(paintable); if (event != null && event.getNativeEvent().getShiftKey()) { applicationConnection.highlightComponent(paintable); @@ -146,8 +143,7 @@ public class VUIDLBrowser extends SimpleTree { String nodeName = uidl.getTag(); try { - Integer.parseInt(nodeName); - nodeName = getNodeName(uidl, conf, nodeName); + nodeName = getNodeName(uidl, conf, Integer.parseInt(nodeName)); } catch (Exception e) { // NOP } @@ -201,7 +197,7 @@ public class VUIDLBrowser extends SimpleTree { tmp.addItem(name + "=" + value); } if (tmp != null) { - add(tmp); + add(tmp); } } catch (final Exception e) { // Ignored, no variables @@ -243,9 +239,9 @@ public class VUIDLBrowser extends SimpleTree { } } - static void highlight(Paintable paintable) { - Widget w = (Widget) paintable; - if (w != null) { + static void highlight(ComponentConnector paintable) { + if (paintable != null) { + Widget w = paintable.getWidget(); Style style = highlight.getStyle(); style.setTop(w.getAbsoluteTop(), Unit.PX); style.setLeft(w.getAbsoluteLeft(), Unit.PX); @@ -261,4 +257,4 @@ public class VUIDLBrowser extends SimpleTree { } } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/terminal/gwt/client/ValueMap.java b/src/com/vaadin/terminal/gwt/client/ValueMap.java index 8d14ef57ce..5deb5feb55 100644 --- a/src/com/vaadin/terminal/gwt/client/ValueMap.java +++ b/src/com/vaadin/terminal/gwt/client/ValueMap.java @@ -101,4 +101,9 @@ public final class ValueMap extends JavaScriptObject { return '' + this[name]; }-*/; + native JavaScriptObject getJavaScriptObject(String name) + /*-{ + return this[name]; + }-*/; + }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java b/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java index a5c75d27dd..dd69883d58 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java @@ -7,5 +7,5 @@ package com.vaadin.terminal.gwt.client; * A helper class used by WidgetMap implementation. Used by the generated code. */ interface WidgetInstantiator { - public Paintable get(); + public ComponentConnector get(); } diff --git a/src/com/vaadin/terminal/gwt/client/WidgetMap.java b/src/com/vaadin/terminal/gwt/client/WidgetMap.java index 51dac20132..af84a11ced 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetMap.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetMap.java @@ -5,19 +5,61 @@ package com.vaadin.terminal.gwt.client; import java.util.HashMap; +import com.vaadin.terminal.gwt.widgetsetutils.WidgetMapGenerator; + +/** + * Abstract class mapping between {@link ComponentConnector} instances and their + * instances. + * + * A concrete implementation of this class is generated by + * {@link WidgetMapGenerator} or one of its subclasses during widgetset + * compilation. + */ abstract class WidgetMap { protected static HashMap<Class, WidgetInstantiator> instmap = new HashMap<Class, WidgetInstantiator>(); - public Paintable instantiate(Class<? extends Paintable> classType) { + /** + * Create a new instance of a connector based on its type. + * + * @param classType + * {@link ComponentConnector} class to instantiate + * @return new instance of the connector + */ + public ComponentConnector instantiate( + Class<? extends ComponentConnector> classType) { return instmap.get(classType).get(); } - public abstract Class<? extends Paintable> getImplementationByServerSideClassName( + /** + * Return the connector class to use for a fully qualified server side + * component class name. + * + * @param fullyqualifiedName + * fully qualified name of the server side component class + * @return component connector class to use + */ + public abstract Class<? extends ComponentConnector> getConnectorClassForServerSideClassName( String fullyqualifiedName); - public abstract Class<? extends Paintable>[] getDeferredLoadedWidgets(); + /** + * Return the connector classes to load after the initial widgetset load and + * start. + * + * @return component connector class to load after the initial widgetset + * loading + */ + public abstract Class<? extends ComponentConnector>[] getDeferredLoadedWidgets(); - public abstract void ensureInstantiator(Class<? extends Paintable> classType); + /** + * Make sure the code for a (deferred or lazy) component connector type has + * been loaded, triggering the load and waiting for its completion if + * necessary. + * + * @param classType + * component connector class + */ + public abstract void ensureInstantiator( + Class<? extends ComponentConnector> classType); } diff --git a/src/com/vaadin/terminal/gwt/client/WidgetSet.java b/src/com/vaadin/terminal/gwt/client/WidgetSet.java index fb77775549..e47837296d 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetSet.java @@ -6,18 +6,7 @@ package com.vaadin.terminal.gwt.client; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ui.VButton; -import com.vaadin.terminal.gwt.client.ui.VCheckBox; -import com.vaadin.terminal.gwt.client.ui.VFilterSelect; -import com.vaadin.terminal.gwt.client.ui.VListSelect; -import com.vaadin.terminal.gwt.client.ui.VPasswordField; -import com.vaadin.terminal.gwt.client.ui.VSplitPanelHorizontal; -import com.vaadin.terminal.gwt.client.ui.VSplitPanelVertical; -import com.vaadin.terminal.gwt.client.ui.VTextArea; -import com.vaadin.terminal.gwt.client.ui.VTextField; -import com.vaadin.terminal.gwt.client.ui.VUnknownComponent; -import com.vaadin.terminal.gwt.client.ui.VView; -import com.vaadin.terminal.gwt.client.ui.VWindow; +import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class WidgetSet { @@ -30,94 +19,61 @@ public class WidgetSet { /** * Create an uninitialized component that best matches given UIDL. The - * component must be a {@link Widget} that implements {@link Paintable}. + * component must be a {@link Widget} that implements + * {@link ComponentConnector}. * - * @param uidl - * UIDL to be painted with returned component. + * @param tag + * component type tag for the component to create * @param client * the application connection that whishes to instantiate widget * * @return New uninitialized and unregistered component that can paint given * UIDL. */ - public Paintable createWidget(UIDL uidl, ApplicationConfiguration conf) { + public ComponentConnector createWidget(int tag, + ApplicationConfiguration conf) { /* * Yes, this (including the generated code in WidgetMap) may look very * odd code, but due the nature of GWT, we cannot do this any cleaner. * Luckily this is mostly written by WidgetSetGenerator, here are just * some hacks. Extra instantiation code is needed if client side widget * has no "native" counterpart on client side. - * - * TODO should try to get rid of these exceptions here */ - final Class<? extends Paintable> classType = resolveWidgetType(uidl, - conf); - if (classType == null || classType == VUnknownComponent.class) { - String serverSideName = conf - .getUnknownServerClassNameByEncodedTagName(uidl.getTag()); - VUnknownComponent c = GWT.create(VUnknownComponent.class); + Class<? extends ComponentConnector> classType = resolveInheritedWidgetType( + conf, tag); + + if (classType == null || classType == UnknownComponentConnector.class) { + String serverSideName = conf.getUnknownServerClassNameByTag(tag); + UnknownComponentConnector c = GWT + .create(UnknownComponentConnector.class); c.setServerSideClassName(serverSideName); return c; - } else if (VWindow.class == classType) { - return GWT.create(VWindow.class); } else { /* * let the auto generated code instantiate this type */ return widgetMap.instantiate(classType); } + } + private Class<? extends ComponentConnector> resolveInheritedWidgetType( + ApplicationConfiguration conf, int tag) { + Class<? extends ComponentConnector> classType = null; + Integer t = tag; + do { + classType = resolveWidgetType(t, conf); + t = conf.getParentTag(t); + } while (classType == null && t != null); + return classType; } - protected Class<? extends Paintable> resolveWidgetType(UIDL uidl, + protected Class<? extends ComponentConnector> resolveWidgetType(int tag, ApplicationConfiguration conf) { - final String tag = uidl.getTag(); - - Class<? extends Paintable> widgetClass = conf + Class<? extends ComponentConnector> widgetClass = conf .getWidgetClassByEncodedTag(tag); - // add our historical quirks - - if (widgetClass == VButton.class && uidl.hasAttribute("type")) { - return VCheckBox.class; - } else if (widgetClass == VView.class && uidl.hasAttribute("sub")) { - return VWindow.class; - } else if (widgetClass == VFilterSelect.class) { - if (uidl.hasAttribute("type")) { - final String type = uidl.getStringAttribute("type").intern(); - if ("legacy-multi" == type) { - return VListSelect.class; - } - } - } else if (widgetClass == VTextField.class) { - if (uidl.hasAttribute("multiline")) { - return VTextArea.class; - } else if (uidl.hasAttribute("secret")) { - return VPasswordField.class; - } - } else if (widgetClass == VSplitPanelHorizontal.class - && uidl.hasAttribute("vertical")) { - return VSplitPanelVertical.class; - } - return widgetClass; - - } - - /** - * Test if the given component implementation conforms to UIDL. - * - * @param currentWidget - * Current implementation of the component - * @param uidl - * UIDL to test against - * @return true iff createWidget would return a new component of the same - * class than currentWidget - */ - public boolean isCorrectImplementation(Widget currentWidget, UIDL uidl, - ApplicationConfiguration conf) { - return currentWidget.getClass() == resolveWidgetType(uidl, conf); } /** @@ -125,44 +81,29 @@ public class WidgetSet { * limitation, widgetset must have function that returns Class by its fully * qualified name. * - * @param fullyQualifiedName + * @param tag * @param applicationConfiguration * @return */ - public Class<? extends Paintable> getImplementationByClassName( - String fullyqualifiedName) { - if (fullyqualifiedName == null) { - return VUnknownComponent.class; - } - Class<? extends Paintable> implementationByServerSideClassName = widgetMap - .getImplementationByServerSideClassName(fullyqualifiedName); - - /* - * Also ensure that our historical quirks have their instantiators - * loaded. Without these, legacy code will throw NPEs when e.g. a Select - * is in multiselect mode, causing the clientside implementation to - * *actually* be VListSelect, when the annotation says VFilterSelect - */ - if (fullyqualifiedName.equals("com.vaadin.ui.Button")) { - loadImplementation(VCheckBox.class); - } else if (fullyqualifiedName.equals("com.vaadin.ui.Select")) { - loadImplementation(VListSelect.class); - } else if (fullyqualifiedName.equals("com.vaadin.ui.TextField")) { - loadImplementation(VTextArea.class); - loadImplementation(VPasswordField.class); - } else if (fullyqualifiedName.equals("com.vaadin.ui.SplitPanel")) { - loadImplementation(VSplitPanelVertical.class); - } - - return implementationByServerSideClassName; - + public Class<? extends ComponentConnector> getConnectorClassByTag(int tag, + ApplicationConfiguration conf) { + Class<? extends ComponentConnector> connectorClass = null; + Integer t = tag; + do { + String serverSideClassName = conf.getServerSideClassNameForTag(t); + connectorClass = widgetMap + .getConnectorClassForServerSideClassName(serverSideClassName); + t = conf.getParentTag(t); + } while (connectorClass == UnknownComponentConnector.class && t != null); + + return connectorClass; } - public Class<? extends Paintable>[] getDeferredLoadedWidgets() { + public Class<? extends ComponentConnector>[] getDeferredLoadedWidgets() { return widgetMap.getDeferredLoadedWidgets(); } - public void loadImplementation(Class<? extends Paintable> nextType) { + public void loadImplementation(Class<? extends ComponentConnector> nextType) { widgetMap.ensureInstantiator(nextType); } diff --git a/src/com/vaadin/terminal/gwt/client/communication/AbstractServerConnectorEvent.java b/src/com/vaadin/terminal/gwt/client/communication/AbstractServerConnectorEvent.java new file mode 100644 index 0000000000..b465e3ad7e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/AbstractServerConnectorEvent.java @@ -0,0 +1,33 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.terminal.gwt.client.ServerConnector; + +public abstract class AbstractServerConnectorEvent<H extends EventHandler> + extends GwtEvent<H> { + private ServerConnector connector; + + protected AbstractServerConnectorEvent() { + } + + public ServerConnector getConnector() { + return connector; + } + + public void setConnector(ServerConnector connector) { + this.connector = connector; + } + + /** + * Sends this event to the given handler. + * + * @param handler + * The handler to dispatch. + */ + @Override + public abstract void dispatch(H handler); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/ClientRpc.java b/src/com/vaadin/terminal/gwt/client/communication/ClientRpc.java new file mode 100644 index 0000000000..45dbe69454 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/ClientRpc.java @@ -0,0 +1,23 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; + +/** + * Interface to be extended by all server to client RPC interfaces. + * + * On the server side, proxies of the interface can be obtained from + * AbstractComponent. On the client, RPC implementations can be registered with + * AbstractConnector.registerRpc(). + * + * Note: Currently, each RPC interface may not contain multiple methods with the + * same name, even if their parameter lists would differ. + * + * @since 7.0 + */ +public interface ClientRpc extends Serializable { + +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/FieldRpc.java b/src/com/vaadin/terminal/gwt/client/communication/FieldRpc.java new file mode 100644 index 0000000000..de464f1fb9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/FieldRpc.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +public class FieldRpc { + public interface FocusServerRpc extends ServerRpc { + public void focus(); + } + + public interface BlurServerRpc extends ServerRpc { + public void blur(); + } + + public interface FocusAndBlurServerRpc extends FocusServerRpc, + BlurServerRpc { + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/InitializableServerRpc.java b/src/com/vaadin/terminal/gwt/client/communication/InitializableServerRpc.java new file mode 100644 index 0000000000..0270de316e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/InitializableServerRpc.java @@ -0,0 +1,26 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import com.vaadin.terminal.gwt.client.ServerConnector; + +/** + * Initialization support for client to server RPC interfaces. + * + * This is in a separate interface used by the GWT generator class. The init + * method is not in {@link ServerRpc} because then also server side proxies + * would have to implement the initialization method. + * + * @since 7.0 + */ +public interface InitializableServerRpc extends ServerRpc { + /** + * Associates the RPC proxy with a connector. Called by generated code. + * Should never be called manually. + * + * @param connector + * The connector the ServerRPC instance is assigned to. + */ + public void initRpc(ServerConnector connector); +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java b/src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java new file mode 100644 index 0000000000..c626d31d0a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java @@ -0,0 +1,60 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import com.google.gwt.json.client.JSONObject; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.server.JsonCodec; + +/** + * Implementors of this interface knows how to serialize an Object of a given + * type to JSON and how to deserialize the JSON back into an object. + * + * The {@link #serialize(Object, ConnectorMap)} and + * {@link #deserialize(JSONObject, ConnectorMap)} methods must be symmetric so + * they can be chained and produce the original result (or an equal result). + * + * Each {@link JSONSerializer} implementation can handle an object of a single + * type - see {@link SerializerMap}. + * + * @since 7.0 + */ +public interface JSONSerializer<T> { + + /** + * Creates and deserializes an object received from the server. Must be + * compatible with {@link #serialize(Object, ConnectorMap)} and also with + * the server side + * {@link JsonCodec#encode(Object, com.vaadin.terminal.gwt.server.PaintableIdMapper)} + * . + * + * @param jsonValue + * JSON map from property name to property value + * @param idMapper + * mapper from paintable id to paintable, used to decode + * references to paintables + * @return A deserialized object + */ + T deserialize(JSONObject jsonValue, ConnectorMap idMapper, + ApplicationConnection connection); + + /** + * Serialize the given object into JSON. Must be compatible with + * {@link #deserialize(JSONObject, ConnectorMap)} and also with the server + * side + * {@link JsonCodec#decode(com.vaadin.external.json.JSONArray, com.vaadin.terminal.gwt.server.PaintableIdMapper)} + * + * @param value + * The object to serialize + * @param idMapper + * mapper from paintable id to paintable, used to decode + * references to paintables + * @return A JSON serialized version of the object + */ + JSONObject serialize(T value, ConnectorMap idMapper, + ApplicationConnection connection); + +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java new file mode 100644 index 0000000000..2b58c13f3e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -0,0 +1,171 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONString; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.ServerConnector; + +/** + * Client side decoder for decodeing shared state and other values from JSON + * received from the server. + * + * Currently, basic data types as well as Map, String[] and Object[] are + * supported, where maps and Object[] can contain other supported data types. + * + * TODO extensible type support + * + * @since 7.0 + */ +public class JsonDecoder { + static SerializerMap serializerMap = GWT.create(SerializerMap.class); + + /** + * Decode a JSON array with two elements (type and value) into a client-side + * type, recursively if necessary. + * + * @param jsonArray + * JSON array with two elements + * @param idMapper + * mapper between connector ID and {@link ServerConnector} + * objects + * @param connection + * reference to the current ApplicationConnection + * @return decoded value (does not contain JSON types) + */ + public static Object decodeValue(JSONArray jsonArray, + ConnectorMap idMapper, ApplicationConnection connection) { + String type = ((JSONString) jsonArray.get(0)).stringValue(); + return decodeValue(type, jsonArray.get(1), idMapper, connection); + } + + private static Object decodeValue(String variableType, Object value, + ConnectorMap idMapper, ApplicationConnection connection) { + Object val = null; + // TODO type checks etc. + if (JsonEncoder.VTYPE_NULL.equals(variableType)) { + val = null; + } else if (JsonEncoder.VTYPE_ARRAY.equals(variableType)) { + val = decodeArray((JSONArray) value, idMapper, connection); + } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { + val = decodeMap((JSONObject) value, idMapper, connection); + } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(variableType)) { + val = decodeConnectorMap((JSONObject) value, idMapper, connection); + } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { + val = decodeList((JSONArray) value, idMapper, connection); + } else if (JsonEncoder.VTYPE_SET.equals(variableType)) { + val = decodeSet((JSONArray) value, idMapper, connection); + } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(variableType)) { + val = decodeStringArray((JSONArray) value); + } else if (JsonEncoder.VTYPE_STRING.equals(variableType)) { + val = ((JSONString) value).stringValue(); + } else if (JsonEncoder.VTYPE_INTEGER.equals(variableType)) { + // TODO handle properly + val = Integer.valueOf(String.valueOf(value)); + } else if (JsonEncoder.VTYPE_LONG.equals(variableType)) { + // TODO handle properly + val = Long.valueOf(String.valueOf(value)); + } else if (JsonEncoder.VTYPE_FLOAT.equals(variableType)) { + // TODO handle properly + val = Float.valueOf(String.valueOf(value)); + } else if (JsonEncoder.VTYPE_DOUBLE.equals(variableType)) { + // TODO handle properly + val = Double.valueOf(String.valueOf(value)); + } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { + // TODO handle properly + val = Boolean.valueOf(String.valueOf(value)); + } else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { + val = idMapper.getConnector(((JSONString) value).stringValue()); + } else { + // object, class name as type + JSONSerializer serializer = serializerMap + .getSerializer(variableType); + // TODO handle case with no serializer found + Object object = serializer.deserialize((JSONObject) value, + idMapper, connection); + return object; + } + + return val; + } + + private static Map<String, Object> decodeMap(JSONObject jsonMap, + ConnectorMap idMapper, ApplicationConnection connection) { + HashMap<String, Object> map = new HashMap<String, Object>(); + Iterator<String> it = jsonMap.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + map.put(key, + decodeValue((JSONArray) jsonMap.get(key), idMapper, + connection)); + } + return map; + } + + private static Map<Connector, Object> decodeConnectorMap( + JSONObject jsonMap, ConnectorMap idMapper, + ApplicationConnection connection) { + HashMap<Connector, Object> map = new HashMap<Connector, Object>(); + Iterator<String> it = jsonMap.keySet().iterator(); + while (it.hasNext()) { + String connectorId = it.next(); + Connector connector = idMapper.getConnector(connectorId); + map.put(connector, + decodeValue((JSONArray) jsonMap.get(connectorId), idMapper, + connection)); + } + return map; + } + + private static String[] decodeStringArray(JSONArray jsonArray) { + int size = jsonArray.size(); + List<String> tokens = new ArrayList<String>(size); + for (int i = 0; i < size; ++i) { + tokens.add(String.valueOf(jsonArray.get(i))); + } + return tokens.toArray(new String[tokens.size()]); + } + + private static Object[] decodeArray(JSONArray jsonArray, + ConnectorMap idMapper, ApplicationConnection connection) { + List<Object> list = decodeList(jsonArray, idMapper, connection); + return list.toArray(new Object[list.size()]); + } + + private static List<Object> decodeList(JSONArray jsonArray, + ConnectorMap idMapper, ApplicationConnection connection) { + List<Object> tokens = new ArrayList<Object>(); + for (int i = 0; i < jsonArray.size(); ++i) { + // each entry always has two elements: type and value + JSONArray entryArray = (JSONArray) jsonArray.get(i); + tokens.add(decodeValue(entryArray, idMapper, connection)); + } + return tokens; + } + + private static Set<Object> decodeSet(JSONArray jsonArray, + ConnectorMap idMapper, ApplicationConnection connection) { + Set<Object> tokens = new HashSet<Object>(); + for (int i = 0; i < jsonArray.size(); ++i) { + // each entry always has two elements: type and value + JSONArray entryArray = (JSONArray) jsonArray.get(i); + tokens.add(decodeValue(entryArray, idMapper, connection)); + } + return tokens; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java new file mode 100644 index 0000000000..fdc06b0e21 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -0,0 +1,207 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONBoolean; +import com.google.gwt.json.client.JSONNull; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONString; +import com.google.gwt.json.client.JSONValue; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ConnectorMap; + +/** + * Encoder for converting RPC parameters and other values to JSON for transfer + * between the client and the server. + * + * Currently, basic data types as well as Map, String[] and Object[] are + * supported, where maps and Object[] can contain other supported data types. + * + * TODO extensible type support + * + * @since 7.0 + */ +public class JsonEncoder { + + public static final String VTYPE_CONNECTOR = "c"; + public static final String VTYPE_BOOLEAN = "b"; + public static final String VTYPE_DOUBLE = "d"; + public static final String VTYPE_FLOAT = "f"; + public static final String VTYPE_LONG = "l"; + public static final String VTYPE_INTEGER = "i"; + public static final String VTYPE_STRING = "s"; + public static final String VTYPE_ARRAY = "a"; + public static final String VTYPE_STRINGARRAY = "S"; + public static final String VTYPE_MAP = "m"; + // Hack to support Map<Connector,?>. Should be replaced by generic support + // for any object as key (#8602) + @Deprecated + public static final String VTYPE_MAP_CONNECTOR = "M"; + public static final String VTYPE_LIST = "L"; + public static final String VTYPE_SET = "q"; + public static final String VTYPE_NULL = "n"; + + /** + * Encode a value to a JSON representation for transport from the client to + * the server. + * + * @param value + * value to convert + * @param connectorMap + * mapper from connectors to connector IDs + * @param connection + * @return JSON representation of the value + */ + public static JSONValue encode(Object value, ConnectorMap connectorMap, + ApplicationConnection connection) { + if (null == value) { + return combineTypeAndValue(VTYPE_NULL, JSONNull.getInstance()); + } else if (value instanceof String[]) { + String[] array = (String[]) value; + JSONArray jsonArray = new JSONArray(); + for (int i = 0; i < array.length; ++i) { + jsonArray.set(i, new JSONString(array[i])); + } + return combineTypeAndValue(VTYPE_STRINGARRAY, jsonArray); + } else if (value instanceof String) { + return combineTypeAndValue(VTYPE_STRING, new JSONString( + (String) value)); + } else if (value instanceof Boolean) { + return combineTypeAndValue(VTYPE_BOOLEAN, + JSONBoolean.getInstance((Boolean) value)); + } else if (value instanceof Object[]) { + return encodeObjectArray((Object[]) value, connectorMap, connection); + } else if (value instanceof Map) { + Map<Object, Object> map = (Map<Object, Object>) value; + JSONObject jsonMap = new JSONObject(); + String type = VTYPE_MAP; + for (Object mapKey : map.keySet()) { + Object mapValue = map.get(mapKey); + if (mapKey instanceof Connector) { + mapKey = ((Connector) mapKey).getConnectorId(); + type = VTYPE_MAP_CONNECTOR; + } + + if (!(mapKey instanceof String)) { + throw new RuntimeException( + "Only Map<String,?> and Map<Connector,?> is currently supported." + + " Failed map used " + + mapKey.getClass().getName() + " as keys"); + } + jsonMap.put((String) mapKey, + encode(mapValue, connectorMap, connection)); + } + return combineTypeAndValue(type, jsonMap); + } else if (value instanceof Connector) { + Connector connector = (Connector) value; + return combineTypeAndValue(VTYPE_CONNECTOR, new JSONString( + connector.getConnectorId())); + } else if (value instanceof Collection) { + return encodeCollection((Collection) value, connectorMap, + connection); + } else { + String transportType = getTransportType(value); + if (transportType != null) { + return combineTypeAndValue(transportType, + new JSONString(String.valueOf(value))); + } else { + // Try to find a generated serializer object, class name is the + // type + transportType = value.getClass().getName(); + JSONSerializer serializer = JsonDecoder.serializerMap + .getSerializer(transportType); + + // TODO handle case with no serializer found + return combineTypeAndValue(transportType, + serializer.serialize(value, connectorMap, connection)); + } + } + } + + private static JSONValue encodeObjectArray(Object[] array, + ConnectorMap connectorMap, ApplicationConnection connection) { + JSONArray jsonArray = new JSONArray(); + for (int i = 0; i < array.length; ++i) { + // TODO handle object graph loops? + jsonArray.set(i, encode(array[i], connectorMap, connection)); + } + return combineTypeAndValue(VTYPE_ARRAY, jsonArray); + } + + private static JSONValue encodeCollection(Collection collection, + ConnectorMap connectorMap, ApplicationConnection connection) { + JSONArray jsonArray = new JSONArray(); + int idx = 0; + for (Object o : collection) { + JSONValue encodedObject = encode(o, connectorMap, connection); + jsonArray.set(idx++, encodedObject); + } + if (collection instanceof Set) { + return combineTypeAndValue(VTYPE_SET, jsonArray); + } else if (collection instanceof List) { + return combineTypeAndValue(VTYPE_LIST, jsonArray); + } else { + throw new RuntimeException("Unsupport collection type: " + + collection.getClass().getName()); + } + + } + + private static JSONValue combineTypeAndValue(String type, JSONValue value) { + JSONArray outerArray = new JSONArray(); + outerArray.set(0, new JSONString(type)); + outerArray.set(1, value); + return outerArray; + } + + /** + * Returns the transport type for the given value. Only returns a transport + * type for internally handled values. + * + * @param value + * The value that should be transported + * @return One of the JsonEncode.VTYPE_ constants or null if the value + * cannot be transported using an internally handled type. + */ + private static String getTransportType(Object value) { + if (value == null) { + return VTYPE_NULL; + } else if (value instanceof String) { + return VTYPE_STRING; + } else if (value instanceof Connector) { + return VTYPE_CONNECTOR; + } else if (value instanceof Boolean) { + return VTYPE_BOOLEAN; + } else if (value instanceof Integer) { + return VTYPE_INTEGER; + } else if (value instanceof Float) { + return VTYPE_FLOAT; + } else if (value instanceof Double) { + return VTYPE_DOUBLE; + } else if (value instanceof Long) { + return VTYPE_LONG; + } else if (value instanceof List) { + return VTYPE_LIST; + } else if (value instanceof Set) { + return VTYPE_SET; + } else if (value instanceof Enum) { + return VTYPE_STRING; // transported as string representation + } else if (value instanceof String[]) { + return VTYPE_STRINGARRAY; + } else if (value instanceof Object[]) { + return VTYPE_ARRAY; + } else if (value instanceof Map) { + return VTYPE_MAP; + } + return null; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java b/src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java new file mode 100644 index 0000000000..e61775a640 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java @@ -0,0 +1,62 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * Information needed by the framework to send an RPC method invocation from the + * client to the server or vice versa. + * + * @since 7.0 + */ +public class MethodInvocation implements Serializable { + + private final String connectorId; + private final String interfaceName; + private final String methodName; + private Object[] parameters; + + public MethodInvocation(String connectorId, String interfaceName, + String methodName) { + this.connectorId = connectorId; + this.interfaceName = interfaceName; + this.methodName = methodName; + } + + public MethodInvocation(String connectorId, String interfaceName, + String methodName, Object[] parameters) { + this(connectorId, interfaceName, methodName); + setParameters(parameters); + } + + public String getConnectorId() { + return connectorId; + } + + public String getInterfaceName() { + return interfaceName; + } + + public String getMethodName() { + return methodName; + } + + public Object[] getParameters() { + return parameters; + } + + public void setParameters(Object[] parameters) { + this.parameters = parameters; + } + + @Override + public String toString() { + return connectorId + ":" + interfaceName + "." + methodName + "(" + + Arrays.toString(parameters) + ")"; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java new file mode 100644 index 0000000000..302e6eaa55 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java @@ -0,0 +1,32 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; + +import com.vaadin.terminal.gwt.client.ConnectorMap; + +/** + * Client side RPC manager that can invoke methods based on RPC calls received + * from the server. + * + * A GWT generator is used to create an implementation of this class at + * run-time. + * + * @since 7.0 + */ +public interface RpcManager extends Serializable { + /** + * Perform server to client RPC invocation. + * + * @param invocation + * method to invoke + * @param connectorMap + * mapper used to find Connector for the method call and any + * connectors referenced in parameters + */ + public void applyInvocation(MethodInvocation invocation, + ConnectorMap connectorMap); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcProxy.java b/src/com/vaadin/terminal/gwt/client/communication/RpcProxy.java new file mode 100644 index 0000000000..113ec1f1b1 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcProxy.java @@ -0,0 +1,37 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ServerConnector; + +/** + * Class for creating proxy instances for Client to Server RPC. + * + * @since 7.0 + */ +public class RpcProxy { + + private static RpcProxyCreator impl = GWT.create(RpcProxyCreator.class); + + /** + * Create a proxy class for the given Rpc interface and assign it to the + * given connector. + * + * @param rpcInterface + * The rpc interface to construct a proxy for + * @param connector + * The connector this proxy is connected to + * @return A proxy class used for calling Rpc methods. + */ + public static <T extends ServerRpc> T create(Class<T> rpcInterface, + ServerConnector connector) { + return impl.create(rpcInterface, connector); + } + + public interface RpcProxyCreator { + <T extends ServerRpc> T create(Class<T> rpcInterface, + ServerConnector connector); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java b/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java new file mode 100644 index 0000000000..0750474d24 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java @@ -0,0 +1,34 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import com.vaadin.terminal.gwt.widgetsetutils.SerializerMapGenerator; + +/** + * Provide a mapping from a type (communicated between the server and the + * client) and a {@link JSONSerializer} instance. + * + * An implementation of this class is created at GWT compilation time by + * {@link SerializerMapGenerator}, so this interface can be instantiated with + * GWT.create(). + * + * @since 7.0 + */ +public interface SerializerMap { + + /** + * Returns a serializer instance for a given type. + * + * @param type + * type communicated on between the server and the client + * (currently fully qualified class name) + * @return serializer instance, not null + * @throws RuntimeException + * if no serializer is found + */ + // TODO better error handling in javadoc and in generator + public JSONSerializer getSerializer(String type); + +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/ServerRpc.java b/src/com/vaadin/terminal/gwt/client/communication/ServerRpc.java new file mode 100644 index 0000000000..664c4a391c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/ServerRpc.java @@ -0,0 +1,15 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; + +/** + * Interface to be extended by all client to server RPC interfaces. + * + * @since 7.0 + */ +public interface ServerRpc extends Serializable { +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/SharedState.java b/src/com/vaadin/terminal/gwt/client/communication/SharedState.java new file mode 100644 index 0000000000..266d6bcbf2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/SharedState.java @@ -0,0 +1,41 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; + +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; + +/** + * Interface to be implemented by all shared state classes used to communicate + * basic information about a {@link Connector} from server to client. + * + * Shared state classes have to be declared in client side packages to be + * accessible both for server and client code. They can be static nested classes + * of a {@link ServerConnector}. + * + * Shared state objects are only sent from the server to the client, and any + * modifications from the client should be performed via an RPC call that + * modifies the authoritative state on the server. + * + * A shared state class should be a bean with getters and setters for each + * field. Supported data types are simple Java types, other beans and maps and + * arrays of these. + * + * On the client side the connector should override + * {@link AbstractComponentConnector#createState()} to create the correct state + * class and {@link AbstractComponentConnector#getState()} override the return + * type. + * + * Subclasses of a {@link Connector} using shared state should also provide a + * subclass of the shared state class of the parent class to extend the state. A + * single {@link Connector} can only have one shared state object. + * + * @since 7.0 + */ +public class SharedState implements Serializable { +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/StateChangeEvent.java b/src/com/vaadin/terminal/gwt/client/communication/StateChangeEvent.java new file mode 100644 index 0000000000..39ecbc022c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/StateChangeEvent.java @@ -0,0 +1,34 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; + +import com.google.gwt.event.shared.EventHandler; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; + +public class StateChangeEvent extends + AbstractServerConnectorEvent<StateChangeHandler> { + /** + * Type of this event, used by the event bus. + */ + public static final Type<StateChangeHandler> TYPE = new Type<StateChangeHandler>(); + + @Override + public Type<StateChangeHandler> getAssociatedType() { + return TYPE; + } + + public StateChangeEvent() { + } + + @Override + public void dispatch(StateChangeHandler listener) { + listener.onStateChanged(this); + } + + public interface StateChangeHandler extends Serializable, EventHandler { + public void onStateChanged(StateChangeEvent stateChangeEvent); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/URLReference.java b/src/com/vaadin/terminal/gwt/client/communication/URLReference.java new file mode 100644 index 0000000000..569c4eff47 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/URLReference.java @@ -0,0 +1,31 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; + +public class URLReference implements Serializable { + + private String URL; + + /** + * Returns the URL that this object refers to. + * <p> + * Note that the URL can use special protocols like theme:// + * + * @return The URL for this reference or null if unknown. + */ + public String getURL() { + return URL; + } + + /** + * Sets the URL that this object refers to + * + * @param URL + */ + public void setURL(String URL) { + this.URL = URL; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java b/src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java new file mode 100644 index 0000000000..4fefc7f845 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java @@ -0,0 +1,32 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONObject; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ConnectorMap; + +public class URLReference_Serializer implements JSONSerializer<URLReference> { + + public URLReference deserialize(JSONObject jsonValue, + ConnectorMap idMapper, ApplicationConnection connection) { + URLReference reference = GWT.create(URLReference.class); + JSONArray jsonURL = (JSONArray) jsonValue.get("URL"); + String URL = (String) JsonDecoder.decodeValue(jsonURL, idMapper, + connection); + reference.setURL(connection.translateVaadinUri(URL)); + return reference; + } + + public JSONObject serialize(URLReference value, ConnectorMap idMapper, + ApplicationConnection connection) { + JSONObject json = new JSONObject(); + json.put("URL", + JsonEncoder.encode(value.getURL(), idMapper, connection)); + return json; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java new file mode 100644 index 0000000000..31204aa0c6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java @@ -0,0 +1,161 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.ContextMenuEvent; +import com.google.gwt.event.dom.client.ContextMenuHandler; +import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.dom.client.DoubleClickEvent; +import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.event.dom.client.MouseUpEvent; +import com.google.gwt.event.dom.client.MouseUpHandler; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Element; +import com.vaadin.terminal.gwt.client.ComponentConnector; + +public abstract class AbstractClickEventHandler implements DoubleClickHandler, + ContextMenuHandler, MouseUpHandler { + + private HandlerRegistration doubleClickHandlerRegistration; + private HandlerRegistration mouseUpHandlerRegistration; + private HandlerRegistration contextMenuHandlerRegistration; + + protected ComponentConnector connector; + private String clickEventIdentifier; + + public AbstractClickEventHandler(ComponentConnector connector, + String clickEventIdentifier) { + this.connector = connector; + this.clickEventIdentifier = clickEventIdentifier; + } + + public void handleEventHandlerRegistration() { + // Handle registering/unregistering of click handler depending on if + // server side listeners have been added or removed. + if (hasEventListener()) { + if (mouseUpHandlerRegistration == null) { + mouseUpHandlerRegistration = registerHandler(this, + MouseUpEvent.getType()); + contextMenuHandlerRegistration = registerHandler(this, + ContextMenuEvent.getType()); + doubleClickHandlerRegistration = registerHandler(this, + DoubleClickEvent.getType()); + } + } else { + if (mouseUpHandlerRegistration != null) { + // Remove existing handlers + doubleClickHandlerRegistration.removeHandler(); + mouseUpHandlerRegistration.removeHandler(); + contextMenuHandlerRegistration.removeHandler(); + + contextMenuHandlerRegistration = null; + mouseUpHandlerRegistration = null; + doubleClickHandlerRegistration = null; + + } + } + + } + + /** + * Registers the given handler to the widget so that the necessary events + * are passed to this {@link ClickEventHandler}. + * <p> + * By default registers the handler with the connector root widget. + * </p> + * + * @param <H> + * @param handler + * The handler to register + * @param type + * The type of the handler. + * @return A reference for the registration of the handler. + */ + protected <H extends EventHandler> HandlerRegistration registerHandler( + final H handler, DomEvent.Type<H> type) { + return connector.getWidget().addDomHandler(handler, type); + } + + /** + * Checks if there is a server side event listener registered for clicks + * + * @return true if there is a server side event listener registered, false + * otherwise + */ + public boolean hasEventListener() { + return connector.hasEventListener(clickEventIdentifier); + } + + /** + * Event handler for context menu. Prevents the browser context menu from + * popping up if there is a listener for right clicks. + */ + public void onContextMenu(ContextMenuEvent event) { + if (hasEventListener() && shouldFireEvent(event)) { + // Prevent showing the browser's context menu when there is a right + // click listener. + event.preventDefault(); + } + } + + /** + * Event handler for mouse up. This is used to detect all single click + * events. + */ + public void onMouseUp(MouseUpEvent event) { + // TODO For perfect accuracy we should check that a mousedown has + // occured on this element before this mouseup and that no mouseup + // has occured anywhere after that. + if (hasEventListener() && shouldFireEvent(event)) { + // "Click" with left, right or middle button + fireClick(event.getNativeEvent()); + } + } + + /** + * Sends the click event based on the given native event. + * + * @param event + * The native event that caused this click event + */ + protected abstract void fireClick(NativeEvent event); + + /** + * Called before firing a click event. Allows sub classes to decide if this + * in an event that should cause an event or not. + * + * @param event + * The user event + * @return true if the event should be fired, false otherwise + */ + protected boolean shouldFireEvent(DomEvent<?> event) { + return true; + } + + /** + * Event handler for double clicks. Used to fire double click events. Note + * that browsers typically fail to prevent the second click event so a + * double click will result in two click events and one double click event. + */ + public void onDoubleClick(DoubleClickEvent event) { + if (hasEventListener() && shouldFireEvent(event)) { + fireClick(event.getNativeEvent()); + } + } + + /** + * Click event calculates and returns coordinates relative to the element + * returned by this method. Default implementation uses the root element of + * the widget. Override to provide a different relative element. + * + * @return The Element used for calculating relative coordinates for a click + * or null if no relative coordinates can be calculated. + */ + protected Element getRelativeToElement() { + return connector.getWidget().getElement(); + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java new file mode 100644 index 0000000000..fdb04f0ddf --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java @@ -0,0 +1,353 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.Set; + +import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.TooltipInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.root.RootConnector; + +public abstract class AbstractComponentConnector extends AbstractConnector + implements ComponentConnector { + + private ComponentContainerConnector parent; + + private Widget widget; + + // shared state from the server to the client + private ComponentState state; + + private String lastKnownWidth = ""; + private String lastKnownHeight = ""; + + /** + * Default constructor + */ + public AbstractComponentConnector() { + } + + /** + * Creates and returns the widget for this VPaintableWidget. This method + * should only be called once when initializing the paintable. + * + * @return + */ + protected abstract Widget createWidget(); + + /** + * Returns the widget associated with this paintable. The widget returned by + * this method must not changed during the life time of the paintable. + * + * @return The widget associated with this paintable + */ + public Widget getWidget() { + if (widget == null) { + widget = createWidget(); + } + + return widget; + } + + /** + * Returns the shared state object for this connector. + * + * If overriding this method to return a more specific type, also + * {@link #createState()} must be overridden. + * + * @return current shared state (not null) + */ + public ComponentState getState() { + return state; + } + + @Deprecated + public static boolean isRealUpdate(UIDL uidl) { + return !uidl.hasAttribute("cached"); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + ConnectorMap paintableMap = ConnectorMap.get(getConnection()); + + if (getState().getDebugId() != null) { + getWidget().getElement().setId(getState().getDebugId()); + } else { + getWidget().getElement().removeAttribute("id"); + + } + + /* + * Disabled state may affect (override) tabindex so the order must be + * first setting tabindex, then enabled state. + */ + if (state instanceof TabIndexState && getWidget() instanceof Focusable) { + ((Focusable) getWidget()).setTabIndex(((TabIndexState) state) + .getTabIndex()); + } + + setWidgetEnabled(isEnabled()); + + // Style names + String styleName = getStyleNames(getWidget().getStylePrimaryName()); + getWidget().setStyleName(styleName); + + // Update tooltip + TooltipInfo tooltipInfo = paintableMap.getTooltipInfo(this, null); + if (getState().hasDescription()) { + tooltipInfo.setTitle(getState().getDescription()); + } else { + tooltipInfo.setTitle(null); + } + // add error info to tooltip if present + tooltipInfo.setErrorMessage(getState().getErrorMessage()); + + // Set captions + if (delegateCaptionHandling()) { + ComponentContainerConnector parent = getParent(); + if (parent != null) { + parent.updateCaption(this); + } else if (!(this instanceof RootConnector)) { + VConsole.error("Parent of connector " + + Util.getConnectorString(this) + + " is null. This is typically an indication of a broken component hierarchy"); + } + } + + /* + * updateComponentSize need to be after caption update so caption can be + * taken into account + */ + + updateComponentSize(); + } + + public void setWidgetEnabled(boolean widgetEnabled) { + if (getWidget() instanceof HasEnabled) { + ((HasEnabled) getWidget()).setEnabled(widgetEnabled); + } + } + + private void updateComponentSize() { + String newWidth = getState().getWidth(); + String newHeight = getState().getHeight(); + + // Parent should be updated if either dimension changed between relative + // and non-relative + if (newWidth.endsWith("%") != lastKnownWidth.endsWith("%")) { + ComponentContainerConnector parent = getParent(); + if (parent instanceof ManagedLayout) { + getLayoutManager().setNeedsHorizontalLayout( + (ManagedLayout) parent); + } + } + + if (newHeight.endsWith("%") != lastKnownHeight.endsWith("%")) { + ComponentContainerConnector parent = getParent(); + if (parent instanceof ManagedLayout) { + getLayoutManager().setNeedsVerticalLayout( + (ManagedLayout) parent); + } + } + + lastKnownWidth = newWidth; + lastKnownHeight = newHeight; + + // Set defined sizes + Widget widget = getWidget(); + + widget.setStyleName("v-has-width", !isUndefinedWidth()); + widget.setStyleName("v-has-height", !isUndefinedHeight()); + + widget.setHeight(newHeight); + widget.setWidth(newWidth); + } + + public boolean isRelativeHeight() { + return getState().getHeight().endsWith("%"); + } + + public boolean isRelativeWidth() { + return getState().getWidth().endsWith("%"); + } + + public boolean isUndefinedHeight() { + return getState().getHeight().length() == 0; + } + + public boolean isUndefinedWidth() { + return getState().getWidth().length() == 0; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Connector#isEnabled() + */ + public boolean isEnabled() { + if (!getState().isEnabled()) { + return false; + } + + if (getParent() == null) { + return true; + } else { + return getParent().isEnabled(); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ComponentConnector#delegateCaptionHandling + * () + */ + public boolean delegateCaptionHandling() { + return true; + } + + /** + * Generates the style name for the widget based on the given primary style + * name and the shared state. + * <p> + * This method can be overridden to provide additional style names for the + * component + * </p> + * + * @param primaryStyleName + * The primary style name to use when generating the final style + * names + * @return The style names, settable using + * {@link Widget#setStyleName(String)} + */ + protected String getStyleNames(String primaryStyleName) { + ComponentState state = getState(); + + StringBuilder styleBuf = new StringBuilder(); + styleBuf.append(primaryStyleName); + styleBuf.append(" v-connector"); + + // Uses connector methods to enable connectors to take hierarchy or + // multiple state variables into account + if (!isEnabled()) { + styleBuf.append(" "); + styleBuf.append(ApplicationConnection.DISABLED_CLASSNAME); + } + if (isReadOnly()) { + styleBuf.append(" "); + styleBuf.append("v-readonly"); + } + + // add additional styles as css classes, prefixed with component default + // stylename + if (state.hasStyles()) { + for (String style : state.getStyles()) { + styleBuf.append(" "); + styleBuf.append(primaryStyleName); + styleBuf.append("-"); + styleBuf.append(style); + styleBuf.append(" "); + styleBuf.append(style); + } + } + + // add error classname to components w/ error + if (null != state.getErrorMessage()) { + styleBuf.append(" "); + styleBuf.append(primaryStyleName); + styleBuf.append(ApplicationConnection.ERROR_CLASSNAME_EXT); + } + + return styleBuf.toString(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ComponentConnector#isReadOnly() + */ + @Deprecated + public boolean isReadOnly() { + return getState().isReadOnly(); + } + + /** + * Sets the shared state for the paintable widget. + * + * @param new shared state (must be compatible with the return value of + * {@link #getState()} - {@link ComponentState} if + * {@link #getState()} is not overridden + */ + public final void setState(SharedState state) { + this.state = (ComponentState) state; + } + + public LayoutManager getLayoutManager() { + return LayoutManager.get(getConnection()); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Connector#getParent() + */ + public ComponentContainerConnector getParent() { + return parent; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.Connector#setParent(com.vaadin.terminal + * .gwt.client.ComponentContainerConnector) + */ + public void setParent(ComponentContainerConnector parent) { + this.parent = parent; + } + + /** + * Checks if there is a registered server side listener for the given event + * identifier. + * + * @param eventIdentifier + * The identifier to check for + * @return true if an event listener has been registered with the given + * event identifier on the server side, false otherwise + */ + public boolean hasEventListener(String eventIdentifier) { + Set<String> reg = getState().getRegisteredEventListeners(); + return (reg != null && reg.contains(eventIdentifier)); + } + + @Override + public void onUnregister() { + super.onUnregister(); + + // Show an error if widget is still attached to DOM. It should never be + // at this point. + if (getWidget() != null && getWidget().isAttached()) { + getWidget().removeFromParent(); + VConsole.error("Widget is still attached to the DOM after the connector (" + + Util.getConnectorString(this) + + ") has been unregistered. Widget was removed."); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java new file mode 100644 index 0000000000..526631e4b2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java @@ -0,0 +1,110 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.LinkedList; +import java.util.List; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; + +public abstract class AbstractComponentContainerConnector extends + AbstractComponentConnector implements ComponentContainerConnector, + ConnectorHierarchyChangeHandler { + + List<ComponentConnector> children; + + private final boolean debugLogging = false; + + /** + * Temporary storage for last enabled state to be able to see if it has + * changed. Can be removed once we are able to listen specifically for + * enabled changes in the state. Widget.isEnabled() cannot be used as all + * Widgets do not implement HasEnabled + */ + private boolean lastWidgetEnabledState = true; + + /** + * Default constructor + */ + public AbstractComponentContainerConnector() { + addConnectorHierarchyChangeHandler(this); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ComponentContainerConnector#getChildren() + */ + public List<ComponentConnector> getChildren() { + if (children == null) { + return new LinkedList<ComponentConnector>(); + } + + return children; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ComponentContainerConnector#setChildren + * (java.util.Collection) + */ + public void setChildren(List<ComponentConnector> children) { + this.children = children; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ComponentContainerConnector# + * connectorHierarchyChanged + * (com.vaadin.terminal.gwt.client.ConnectorHierarchyChangedEvent) + */ + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + if (debugLogging) { + VConsole.log("Hierarchy changed for " + + Util.getConnectorString(this)); + String oldChildren = "* Old children: "; + for (ComponentConnector child : event.getOldChildren()) { + oldChildren += Util.getConnectorString(child) + " "; + } + VConsole.log(oldChildren); + + String newChildren = "* New children: "; + for (ComponentConnector child : getChildren()) { + newChildren += Util.getConnectorString(child) + " "; + } + VConsole.log(newChildren); + } + } + + public HandlerRegistration addConnectorHierarchyChangeHandler( + ConnectorHierarchyChangeHandler handler) { + return ensureHandlerManager().addHandler( + ConnectorHierarchyChangeEvent.TYPE, handler); + } + + @Override + public void setWidgetEnabled(boolean widgetEnabled) { + if (lastWidgetEnabledState == widgetEnabled) { + return; + } + lastWidgetEnabledState = widgetEnabled; + + super.setWidgetEnabled(widgetEnabled); + for (ComponentConnector c : getChildren()) { + // Update children as they might be affected by the enabled state of + // their parent + c.setWidgetEnabled(c.isEnabled()); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java new file mode 100644 index 0000000000..ef74228ae8 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java @@ -0,0 +1,186 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.event.shared.GwtEvent; +import com.google.gwt.event.shared.HandlerManager; +import com.google.web.bindery.event.shared.HandlerRegistration; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; + +/** + * An abstract implementation of Connector. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public abstract class AbstractConnector implements ServerConnector, + StateChangeHandler { + + private ApplicationConnection connection; + private String id; + + private HandlerManager handlerManager; + private Map<String, Collection<ClientRpc>> rpcImplementations; + private final boolean debugLogging = false; + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.VPaintable#getConnection() + */ + public final ApplicationConnection getConnection() { + return connection; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Connector#getId() + */ + public String getConnectorId() { + return id; + } + + /** + * Called once by the framework to initialize the connector. + * <p> + * Note that the shared state is not yet available when this method is + * called. + * <p> + * Connector classes should override {@link #init()} instead of this method. + */ + public final void doInit(String connectorId, + ApplicationConnection connection) { + this.connection = connection; + id = connectorId; + + addStateChangeHandler(this); + init(); + } + + /** + * Called when the connector has been initialized. Override this method to + * perform initialization of the connector. + */ + // FIXME: It might make sense to make this abstract to force users to + // use init instead of constructor, where connection and id has not yet been + // set. + protected void init() { + + } + + /** + * Registers an implementation for a server to client RPC interface. + * + * Multiple registrations can be made for a single interface, in which case + * all of them receive corresponding RPC calls. + * + * @param rpcInterface + * RPC interface + * @param implementation + * implementation that should receive RPC calls + * @param <T> + * The type of the RPC interface that is being registered + */ + protected <T extends ClientRpc> void registerRpc(Class<T> rpcInterface, + T implementation) { + String rpcInterfaceId = rpcInterface.getName().replaceAll("\\$", "."); + if (null == rpcImplementations) { + rpcImplementations = new HashMap<String, Collection<ClientRpc>>(); + } + if (null == rpcImplementations.get(rpcInterfaceId)) { + rpcImplementations.put(rpcInterfaceId, new ArrayList<ClientRpc>()); + } + rpcImplementations.get(rpcInterfaceId).add(implementation); + } + + /** + * Unregisters an implementation for a server to client RPC interface. + * + * @param rpcInterface + * RPC interface + * @param implementation + * implementation to unregister + */ + protected <T extends ClientRpc> void unregisterRpc(Class<T> rpcInterface, + T implementation) { + String rpcInterfaceId = rpcInterface.getName().replaceAll("\\$", "."); + if (null != rpcImplementations + && null != rpcImplementations.get(rpcInterfaceId)) { + rpcImplementations.get(rpcInterfaceId).remove(implementation); + } + } + + public <T extends ClientRpc> Collection<T> getRpcImplementations( + String rpcInterfaceId) { + if (null == rpcImplementations) { + return Collections.emptyList(); + } + return (Collection<T>) rpcImplementations.get(rpcInterfaceId); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Connector#isConnectorEnabled() + */ + public boolean isConnectorEnabled() { + // Client side can always receive message from the server + return true; + } + + public void fireEvent(GwtEvent<?> event) { + if (handlerManager != null) { + handlerManager.fireEvent(event); + } + } + + protected HandlerManager ensureHandlerManager() { + if (handlerManager == null) { + handlerManager = new HandlerManager(this); + } + + return handlerManager; + } + + public HandlerRegistration addStateChangeHandler(StateChangeHandler handler) { + return ensureHandlerManager() + .addHandler(StateChangeEvent.TYPE, handler); + } + + public void onStateChanged(StateChangeEvent stateChangeEvent) { + if (debugLogging) { + VConsole.log("State change event for " + + Util.getConnectorString(stateChangeEvent.getConnector()) + + " received by " + Util.getConnectorString(this)); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ServerConnector#onUnregister() + */ + public void onUnregister() { + if (debugLogging) { + VConsole.log("Unregistered connector " + + Util.getConnectorString(this)); + } + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractFieldConnector.java new file mode 100644 index 0000000000..4be0f02c2a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractFieldConnector.java @@ -0,0 +1,54 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.ApplicationConnection; + +public abstract class AbstractFieldConnector extends AbstractComponentConnector { + + @Override + public AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().isPropertyReadOnly(); + } + + public boolean isModified() { + return getState().isModified(); + } + + /** + * Checks whether the required indicator should be shown for the field. + * + * Required indicators are hidden if the field or its data source is + * read-only. + * + * @return true if required indicator should be shown + */ + public boolean isRequired() { + return getState().isRequired() && !isReadOnly(); + } + + @Override + protected String getStyleNames(String primaryStyleName) { + String styleNames = super.getStyleNames(primaryStyleName); + + if (isModified()) { + // add modified classname to Fields + styleNames += " " + ApplicationConnection.MODIFIED_CLASSNAME; + } + + if (isRequired()) { + // add required classname to Fields + styleNames += " " + primaryStyleName + + ApplicationConnection.REQUIRED_CLASSNAME_EXT; + } + + return styleNames; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractLayoutConnector.java new file mode 100644 index 0000000000..175e67807f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractLayoutConnector.java @@ -0,0 +1,13 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +public abstract class AbstractLayoutConnector extends + AbstractComponentContainerConnector { + + @Override + public AbstractLayoutState getState() { + return (AbstractLayoutState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractLayoutState.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractLayoutState.java new file mode 100644 index 0000000000..fee5ea746a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractLayoutState.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ComponentState; + +public class AbstractLayoutState extends ComponentState { + private int marginsBitmask; + + public int getMarginsBitmask() { + return marginsBitmask; + } + + public void setMarginsBitmask(int marginsBitmask) { + this.marginsBitmask = marginsBitmask; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java index 3deb140d30..cffdb1e68a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java @@ -3,134 +3,48 @@ */ package com.vaadin.terminal.gwt.client.ui; -import java.util.HashMap; -import java.util.Map; - import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.ContextMenuEvent; -import com.google.gwt.event.dom.client.ContextMenuHandler; -import com.google.gwt.event.dom.client.DomEvent; -import com.google.gwt.event.dom.client.DoubleClickEvent; -import com.google.gwt.event.dom.client.DoubleClickHandler; -import com.google.gwt.event.dom.client.MouseUpEvent; -import com.google.gwt.event.dom.client.MouseUpHandler; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; - -public abstract class ClickEventHandler implements DoubleClickHandler, - ContextMenuHandler, MouseUpHandler { +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; - private HandlerRegistration doubleClickHandlerRegistration; - private HandlerRegistration mouseUpHandlerRegistration; - private HandlerRegistration contextMenuHandlerRegistration; +public abstract class ClickEventHandler extends AbstractClickEventHandler { - protected String clickEventIdentifier; - protected Paintable paintable; - private ApplicationConnection client; + public static final String CLICK_EVENT_IDENTIFIER = "click"; - public ClickEventHandler(Paintable paintable, String clickEventIdentifier) { - this.paintable = paintable; - this.clickEventIdentifier = clickEventIdentifier; + public ClickEventHandler(ComponentConnector connector) { + this(connector, CLICK_EVENT_IDENTIFIER); } - public void handleEventHandlerRegistration(ApplicationConnection client) { - this.client = client; - // Handle registering/unregistering of click handler depending on if - // server side listeners have been added or removed. - if (hasEventListener()) { - if (mouseUpHandlerRegistration == null) { - mouseUpHandlerRegistration = registerHandler(this, - MouseUpEvent.getType()); - contextMenuHandlerRegistration = registerHandler(this, - ContextMenuEvent.getType()); - doubleClickHandlerRegistration = registerHandler(this, - DoubleClickEvent.getType()); - } - } else { - if (mouseUpHandlerRegistration != null) { - // Remove existing handlers - doubleClickHandlerRegistration.removeHandler(); - mouseUpHandlerRegistration.removeHandler(); - contextMenuHandlerRegistration.removeHandler(); - - contextMenuHandlerRegistration = null; - mouseUpHandlerRegistration = null; - doubleClickHandlerRegistration = null; - - } - } - - } - - protected abstract <H extends EventHandler> HandlerRegistration registerHandler( - final H handler, DomEvent.Type<H> type); - - protected ApplicationConnection getApplicationConnection() { - return client; - } - - public boolean hasEventListener() { - return getApplicationConnection().hasEventListeners(paintable, - clickEventIdentifier); + public ClickEventHandler(ComponentConnector connector, + String clickEventIdentifier) { + super(connector, clickEventIdentifier); } + /** + * Sends the click event based on the given native event. Delegates actual + * sending to {@link #fireClick(MouseEventDetails)}. + * + * @param event + * The native event that caused this click event + */ protected void fireClick(NativeEvent event) { - ApplicationConnection client = getApplicationConnection(); - String pid = getApplicationConnection().getPid(paintable); - - MouseEventDetails mouseDetails = new MouseEventDetails(event, - getRelativeToElement()); - - Map<String, Object> parameters = new HashMap<String, Object>(); - parameters.put("mouseDetails", mouseDetails.serialize()); - client.updateVariable(pid, clickEventIdentifier, parameters, true); - - } - - public void onContextMenu(ContextMenuEvent event) { - if (hasEventListener()) { - // Prevent showing the browser's context menu when there is a right - // click listener. - event.preventDefault(); - } - - } - - public void onMouseUp(MouseUpEvent event) { - // TODO For perfect accuracy we should check that a mousedown has - // occured on this element before this mouseup and that no mouseup - // has occured anywhere after that. - if (hasEventListener()) { - // "Click" with left, right or middle button - fireClick(event.getNativeEvent()); - } - } - - public void onDoubleClick(DoubleClickEvent event) { - if (hasEventListener()) { - fireClick(event.getNativeEvent()); - } + MouseEventDetails mouseDetails = MouseEventDetailsBuilder + .buildMouseEventDetails(event, getRelativeToElement()); + fireClick(event, mouseDetails); } /** - * Click event calculates and returns coordinates relative to the element - * returned by this method. Default implementation uses the root element of - * the widget. Override to provide a different relative element. + * Sends the click event to the server. Must be implemented by sub classes, + * typically by calling an RPC method. * - * @return The Element used for calculating relative coordinates for a click - * or null if no relative coordinates can be calculated. + * @param event + * The event that caused this click to be fired + * + * @param mouseDetails + * The mouse details for the event */ - protected Element getRelativeToElement() { - if (paintable instanceof Widget) { - return ((Widget) paintable).getElement(); - } - - return null; - } + protected abstract void fireClick(NativeEvent event, + MouseEventDetails mouseDetails); }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/ClickRpc.java b/src/com/vaadin/terminal/gwt/client/ui/ClickRpc.java new file mode 100644 index 0000000000..37d6443f55 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/ClickRpc.java @@ -0,0 +1,18 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public interface ClickRpc extends ServerRpc { + /** + * Called when a click event has occurred and there are server side + * listeners for the event. + * + * @param mouseDetails + * Details about the mouse when the event took place + */ + public void click(MouseEventDetails mouseDetails); +}
\ No newline at end of file diff --git a/src/com/vaadin/ui/ClientWidget.java b/src/com/vaadin/terminal/gwt/client/ui/Connect.java index 8817f8f776..0581bdb99c 100644 --- a/src/com/vaadin/ui/ClientWidget.java +++ b/src/com/vaadin/terminal/gwt/client/ui/Connect.java @@ -1,42 +1,39 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -/** - * - */ -package com.vaadin.ui; +package com.vaadin.terminal.gwt.client.ui; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.widgetsetutils.CustomWidgetMapGenerator; import com.vaadin.terminal.gwt.widgetsetutils.EagerWidgetMapGenerator; import com.vaadin.terminal.gwt.widgetsetutils.LazyWidgetMapGenerator; import com.vaadin.terminal.gwt.widgetsetutils.WidgetMapGenerator; /** - * Annotation defining the default client side counterpart in GWT terminal for - * {@link Component}. + * Annotation defining the server side connector that this ClientSideConnector + * should connect to. The value must always by a class extending + * {@link ClientConnector}. * <p> - * With this annotation server side Vaadin component is marked to have a client - * side counterpart. The value of the annotation is the class of client side + * With this annotation client side Vaadin connector is marked to have a server + * side counterpart. The value of the annotation is the class of server side * implementation. - * <p> - * Note, even though client side implementation is needed during development, - * one may safely remove them from the classpath of the production server. * - * @since 6.2 + * @since 7.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface ClientWidget { +public @interface Connect { + /** - * @return the client side counterpart for the annotated component + * @return the server side counterpart for the annotated component connector */ - Class<? extends Paintable> value(); + Class<? extends Connector> value(); /** * Depending on the used WidgetMap generator, these optional hints may be @@ -49,7 +46,7 @@ public @interface ClientWidget { * is not included in the initial JavaScript application loaded when the * application starts. Instead the implementation is loaded to the client * when it is first needed. Lazy loaded widget can be achieved by giving - * {@link LoadStyle#LAZY} value in ClientWidget annotation. + * {@link LoadStyle#LAZY} value in {@link Connect} annotation. * <p> * Lazy loaded widgets don't stress the size and startup time of the client * side as much as eagerly loaded widgets. On the other hand there is a @@ -93,5 +90,4 @@ public @interface ClientWidget { */ LAZY } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusElementPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusElementPanel.java index 1b97406ae0..4984c4ce3b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusElementPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusElementPanel.java @@ -13,11 +13,9 @@ import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.FocusImpl; -import com.vaadin.terminal.gwt.client.BrowserInfo; /** * A panel that contains an always visible 0x0 size element that holds the focus - * for all browsers but IE6. */ public class FocusElementPanel extends SimpleFocusablePanel { @@ -30,22 +28,20 @@ public class FocusElementPanel extends SimpleFocusablePanel { @Override public void setWidget(Widget w) { super.setWidget(w); - if (!BrowserInfo.get().isIE6()) { - if (focusElement.getParentElement() == null) { - Style style = focusElement.getStyle(); - style.setPosition(Position.FIXED); - style.setTop(0, Unit.PX); - style.setLeft(0, Unit.PX); - getElement().appendChild(focusElement); - /* Sink from focusElement too as focus and blur don't bubble */ - DOM.sinkEvents( - (com.google.gwt.user.client.Element) focusElement - .cast(), Event.FOCUSEVENTS); - // revert to original, not focusable - getElement().setPropertyObject("tabIndex", null); - } else { - moveFocusElementAfterWidget(); - } + if (focusElement.getParentElement() == null) { + Style style = focusElement.getStyle(); + style.setPosition(Position.FIXED); + style.setTop(0, Unit.PX); + style.setLeft(0, Unit.PX); + getElement().appendChild(focusElement); + /* Sink from focusElement too as focus and blur don't bubble */ + DOM.sinkEvents( + (com.google.gwt.user.client.Element) focusElement.cast(), + Event.FOCUSEVENTS); + // revert to original, not focusable + getElement().setPropertyObject("tabIndex", null); + } else { + moveFocusElementAfterWidget(); } } @@ -58,28 +54,20 @@ public class FocusElementPanel extends SimpleFocusablePanel { @Override public void setFocus(boolean focus) { - if (BrowserInfo.get().isIE6()) { - super.setFocus(focus); + if (focus) { + FocusImpl.getFocusImplForPanel().focus( + (Element) focusElement.cast()); } else { - if (focus) { - FocusImpl.getFocusImplForPanel().focus( - (Element) focusElement.cast()); - } else { - FocusImpl.getFocusImplForPanel().blur( - (Element) focusElement.cast()); - } + FocusImpl.getFocusImplForPanel() + .blur((Element) focusElement.cast()); } } @Override public void setTabIndex(int tabIndex) { - if (BrowserInfo.get().isIE6()) { - super.setTabIndex(tabIndex); - } else { - getElement().setTabIndex(-1); - if (focusElement != null) { - focusElement.setTabIndex(tabIndex); - } + getElement().setTabIndex(-1); + if (focusElement != null) { + focusElement.setTabIndex(tabIndex); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 96cb4b8a35..533d6a78ae 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -24,7 +24,6 @@ import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.FocusImpl; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.VConsole; /** * A scrollhandlers similar to {@link ScrollPanel}. @@ -60,18 +59,9 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements if (useFakeFocusElement()) { if (focusElement.getParentElement() == null) { Style style = focusElement.getStyle(); - if (BrowserInfo.get().isIE6()) { - style.setOverflow(Overflow.HIDDEN); - style.setHeight(0, Unit.PX); - style.setWidth(0, Unit.PX); - style.setPosition(Position.ABSOLUTE); - - addScrollHandler(this); - } else { - style.setPosition(Position.FIXED); - style.setTop(0, Unit.PX); - style.setLeft(0, Unit.PX); - } + style.setPosition(Position.FIXED); + style.setTop(0, Unit.PX); + style.setLeft(0, Unit.PX); getElement().appendChild(focusElement); /* Sink from focusElemet too as focusa and blur don't bubble */ DOM.sinkEvents( diff --git a/src/com/vaadin/terminal/gwt/client/ui/Icon.java b/src/com/vaadin/terminal/gwt/client/ui/Icon.java index fd2229fc8d..b64605aac9 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/Icon.java +++ b/src/com/vaadin/terminal/gwt/client/ui/Icon.java @@ -19,7 +19,6 @@ public class Icon extends UIObject { DOM.setElementProperty(getElement(), "alt", ""); setStyleName(CLASSNAME); this.client = client; - client.addPngFix(getElement()); } public Icon(ApplicationConnection client, String uidlUri) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java index 3a4048907f..7a5d85e34b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java @@ -3,39 +3,37 @@ */ package com.vaadin.terminal.gwt.client.ui; -import java.util.HashMap; -import java.util.Map; - import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.user.client.Element; -import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; + +public abstract class LayoutClickEventHandler extends AbstractClickEventHandler { -public abstract class LayoutClickEventHandler extends ClickEventHandler { + public static final String LAYOUT_CLICK_EVENT_IDENTIFIER = "lClick"; - public LayoutClickEventHandler(Paintable paintable, + public LayoutClickEventHandler(ComponentConnector connector) { + this(connector, LAYOUT_CLICK_EVENT_IDENTIFIER); + } + + public LayoutClickEventHandler(ComponentConnector connector, String clickEventIdentifier) { - super(paintable, clickEventIdentifier); + super(connector, clickEventIdentifier); } - protected abstract Paintable getChildComponent(Element element); + protected abstract ComponentConnector getChildComponent(Element element); + + protected ComponentConnector getChildComponent(NativeEvent event) { + return getChildComponent((Element) event.getEventTarget().cast()); + } @Override protected void fireClick(NativeEvent event) { - ApplicationConnection client = getApplicationConnection(); - String pid = getApplicationConnection().getPid(paintable); - - MouseEventDetails mouseDetails = new MouseEventDetails(event, - getRelativeToElement()); - Paintable childComponent = getChildComponent((Element) event - .getEventTarget().cast()); - - Map<String, Object> parameters = new HashMap<String, Object>(); - parameters.put("mouseDetails", mouseDetails.serialize()); - parameters.put("component", childComponent); - - client.updateVariable(pid, clickEventIdentifier, parameters, true); + MouseEventDetails mouseDetails = MouseEventDetailsBuilder + .buildMouseEventDetails(event, getRelativeToElement()); + getLayoutClickRPC().layoutClick(mouseDetails, getChildComponent(event)); } + protected abstract LayoutClickRpc getLayoutClickRPC(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickRpc.java b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickRpc.java new file mode 100644 index 0000000000..5b76f398a9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickRpc.java @@ -0,0 +1,22 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public interface LayoutClickRpc extends ServerRpc { + /** + * Called when a layout click event has occurred and there are server side + * listeners for the event. + * + * @param mouseDetails + * Details about the mouse when the event took place + * @param clickedConnector + * The child component that was the target of the event + */ + public void layoutClick(MouseEventDetails mouseDetails, + Connector clickedConnector); +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/ManagedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/ManagedLayout.java new file mode 100644 index 0000000000..6d0271ee40 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/ManagedLayout.java @@ -0,0 +1,10 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ComponentConnector; + +public interface ManagedLayout extends ComponentConnector { + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/MediaBaseConnector.java b/src/com/vaadin/terminal/gwt/client/ui/MediaBaseConnector.java new file mode 100644 index 0000000000..1e067bf6fb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/MediaBaseConnector.java @@ -0,0 +1,130 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; + +public abstract class MediaBaseConnector extends AbstractComponentConnector + implements Paintable { + + public static final String TAG_SOURCE = "src"; + + public static final String ATTR_MUTED = "muted"; + public static final String ATTR_CONTROLS = "ctrl"; + public static final String ATTR_AUTOPLAY = "auto"; + public static final String ATTR_RESOURCE = "res"; + public static final String ATTR_RESOURCE_TYPE = "type"; + public static final String ATTR_HTML = "html"; + public static final String ATTR_ALT_TEXT = "alt"; + + /** + * Server to client RPC interface for controlling playback of the media. + * + * @since 7.0 + */ + public static interface MediaControl extends ClientRpc { + /** + * Start playing the media. + */ + public void play(); + + /** + * Pause playback of the media. + */ + public void pause(); + } + + @Override + protected void init() { + super.init(); + + registerRpc(MediaControl.class, new MediaControl() { + public void play() { + getWidget().play(); + } + + public void pause() { + getWidget().pause(); + } + }); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().setControls(shouldShowControls(uidl)); + getWidget().setAutoplay(shouldAutoplay(uidl)); + getWidget().setMuted(isMediaMuted(uidl)); + + // Add all sources + for (int ix = 0; ix < uidl.getChildCount(); ix++) { + UIDL child = uidl.getChildUIDL(ix); + if (TAG_SOURCE.equals(child.getTag())) { + getWidget() + .addSource(getSourceUrl(child), getSourceType(child)); + } + } + setAltText(uidl); + } + + protected boolean shouldShowControls(UIDL uidl) { + return uidl.getBooleanAttribute(ATTR_CONTROLS); + } + + private boolean shouldAutoplay(UIDL uidl) { + return uidl.getBooleanAttribute(ATTR_AUTOPLAY); + } + + private boolean isMediaMuted(UIDL uidl) { + return uidl.getBooleanAttribute(ATTR_MUTED); + } + + private boolean allowHtmlContent(UIDL uidl) { + return uidl.getBooleanAttribute(ATTR_HTML); + } + + @Override + public VMediaBase getWidget() { + return (VMediaBase) super.getWidget(); + } + + /** + * @param uidl + * @return the URL of a resource to be used as a source for the media + */ + private String getSourceUrl(UIDL uidl) { + String url = getConnection().translateVaadinUri( + uidl.getStringAttribute(MediaBaseConnector.ATTR_RESOURCE)); + if (url == null) { + return ""; + } + return url; + } + + /** + * @param uidl + * @return the mime type of the media + */ + private String getSourceType(UIDL uidl) { + return uidl.getStringAttribute(MediaBaseConnector.ATTR_RESOURCE_TYPE); + } + + private void setAltText(UIDL uidl) { + String alt = uidl.getStringAttribute(MediaBaseConnector.ATTR_ALT_TEXT); + + if (alt == null || "".equals(alt)) { + alt = getWidget().getDefaultAltHtml(); + } else if (!allowHtmlContent(uidl)) { + alt = Util.escapeHTML(alt); + } + getWidget().setAltText(alt); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/PostLayoutListener.java b/src/com/vaadin/terminal/gwt/client/ui/PostLayoutListener.java new file mode 100644 index 0000000000..feb7494f87 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/PostLayoutListener.java @@ -0,0 +1,8 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +public interface PostLayoutListener { + public void postLayout(); +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java b/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java index 934fcaa8b7..37e9ab4a69 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java @@ -15,11 +15,9 @@ import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.KeyboardListener; import com.google.gwt.user.client.ui.KeyboardListenerCollection; -import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; @@ -34,9 +32,8 @@ import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; public class ShortcutActionHandler { /** - * An interface implemented by those users (most often {@link Container}s, - * but HasWidgets at least) of this helper class that want to support - * special components like {@link VRichTextArea} that don't properly + * An interface implemented by those users of this helper class that want to + * support special components like {@link VRichTextArea} that don't properly * propagate key down events. Those components can build support for * shortcut actions by traversing the closest * {@link ShortcutActionHandlerOwner} from the component hierarchy an @@ -52,12 +49,12 @@ public class ShortcutActionHandler { } /** - * A focusable {@link Paintable} implementing this interface will be - * notified before shortcut actions are handled if it will be the target of - * the action (most commonly means it is the focused component during the + * A focusable {@link ComponentConnector} implementing this interface will + * be notified before shortcut actions are handled if it will be the target + * of the action (most commonly means it is the focused component during the * keyboard combination is triggered by the user). */ - public interface BeforeShortcutActionListener extends Paintable { + public interface BeforeShortcutActionListener extends ComponentConnector { /** * This method is called by ShortcutActionHandler before firing the * shortcut if the Paintable is currently focused (aka the target of the @@ -111,7 +108,7 @@ public class ShortcutActionHandler { } } - public void handleKeyboardEvent(final Event event, Paintable target) { + public void handleKeyboardEvent(final Event event, ComponentConnector target) { final int modifiers = KeyboardListenerCollection .getKeyboardModifiers(event); final char keyCode = (char) DOM.eventGetKeyCode(event); @@ -133,16 +130,12 @@ public class ShortcutActionHandler { } private void fireAction(final Event event, final ShortcutAction a, - Paintable target) { + ComponentConnector target) { final Element et = DOM.eventGetTarget(event); if (target == null) { - Widget w = Util.findWidget(et, null); - while (w != null && !(w instanceof Paintable)) { - w = w.getParent(); - } - target = (Paintable) w; + target = Util.findPaintable(client, et); } - final Paintable finalTarget = target; + final ComponentConnector finalTarget = target; event.preventDefault(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/SimpleManagedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/SimpleManagedLayout.java new file mode 100644 index 0000000000..9ccb29a750 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/SimpleManagedLayout.java @@ -0,0 +1,8 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +public interface SimpleManagedLayout extends ManagedLayout { + public void layout(); +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TabIndexState.java b/src/com/vaadin/terminal/gwt/client/ui/TabIndexState.java new file mode 100644 index 0000000000..7ffb328add --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/TabIndexState.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +/** + * Interface implemented by state classes that support tab indexes. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public interface TabIndexState { + /** + * Gets the <i>tabulator index</i> of the field. + * + * @return the tab index for the Field + */ + public int getTabIndex(); + + /** + * Sets the <i>tabulator index</i> of the field. + * + * @param tabIndex + * the tab index to set + */ + public void setTabIndex(int tabIndex); +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/Table.java b/src/com/vaadin/terminal/gwt/client/ui/Table.java deleted file mode 100644 index 4b3ba0422e..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/Table.java +++ /dev/null @@ -1,15 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.user.client.ui.HasWidgets; -import com.vaadin.terminal.gwt.client.Paintable; - -public interface Table extends Paintable, HasWidgets { - final int SELECT_MODE_NONE = 0; - final int SELECT_MODE_SINGLE = 1; - final int SELECT_MODE_MULTI = 2; - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/UnknownComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/UnknownComponentConnector.java new file mode 100644 index 0000000000..94eff44eee --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/UnknownComponentConnector.java @@ -0,0 +1,39 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; + +public class UnknownComponentConnector extends AbstractComponentConnector { + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + protected Widget createWidget() { + return GWT.create(VUnknownComponent.class); + } + + @Override + public VUnknownComponent getWidget() { + return (VUnknownComponent) super.getWidget(); + } + + public void setServerSideClassName(String serverClassName) { + getWidget() + .setCaption( + "Widgetset does not contain implementation for " + + serverClassName + + ". Check its component connector's @Connect mapping, widgetsets " + + "GWT module description file and re-compile your" + + " widgetset. In case you have downloaded a vaadin" + + " add-on package, you might want to refer to " + + "<a href='http://vaadin.com/using-addons'>add-on " + + "instructions</a>."); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java deleted file mode 100644 index a6abc411f8..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java +++ /dev/null @@ -1,442 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Style; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.VConsole; - -public class VAbsoluteLayout extends ComplexPanel implements Container { - - /** Tag name for widget creation */ - public static final String TAGNAME = "absolutelayout"; - - /** Class name, prefix in styling */ - public static final String CLASSNAME = "v-absolutelayout"; - - private DivElement marginElement; - - protected final Element canvas = DOM.createDiv(); - - // private int excessPixelsHorizontal; - // - // private int excessPixelsVertical; - - private Object previousStyleName; - - private Map<String, AbsoluteWrapper> pidToComponentWrappper = new HashMap<String, AbsoluteWrapper>(); - - protected ApplicationConnection client; - - private boolean rendering; - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this, EventId.LAYOUT_CLICK) { - - @Override - protected Paintable getChildComponent(Element element) { - return getComponent(element); - } - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - }; - - public VAbsoluteLayout() { - setElement(Document.get().createDivElement()); - setStyleName(CLASSNAME); - marginElement = Document.get().createDivElement(); - canvas.getStyle().setProperty("position", "relative"); - canvas.getStyle().setProperty("overflow", "hidden"); - marginElement.appendChild(canvas); - getElement().appendChild(marginElement); - } - - public RenderSpace getAllocatedSpace(Widget child) { - // TODO needs some special handling for components with only on edge - // horizontally or vertically defined - AbsoluteWrapper wrapper = (AbsoluteWrapper) child.getParent(); - int w; - if (wrapper.left != null && wrapper.right != null) { - w = wrapper.getOffsetWidth(); - } else if (wrapper.right != null) { - // left == null - // available width == right edge == offsetleft + width - w = wrapper.getOffsetWidth() + wrapper.getElement().getOffsetLeft(); - } else { - // left != null && right == null || left == null && - // right == null - // available width == canvas width - offset left - w = canvas.getOffsetWidth() - wrapper.getElement().getOffsetLeft(); - } - int h; - if (wrapper.top != null && wrapper.bottom != null) { - h = wrapper.getOffsetHeight(); - } else if (wrapper.bottom != null) { - // top not defined, available space 0... bottom of wrapper - h = wrapper.getElement().getOffsetTop() + wrapper.getOffsetHeight(); - } else { - // top defined or both undefined, available space == canvas - top - h = canvas.getOffsetHeight() - wrapper.getElement().getOffsetTop(); - } - - return new RenderSpace(w, h); - } - - public boolean hasChildComponent(Widget component) { - for (Iterator<Entry<String, AbsoluteWrapper>> iterator = pidToComponentWrappper - .entrySet().iterator(); iterator.hasNext();) { - if (iterator.next().getValue().paintable == component) { - return true; - } - } - return false; - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - for (Widget wrapper : getChildren()) { - AbsoluteWrapper w = (AbsoluteWrapper) wrapper; - if (w.getWidget() == oldComponent) { - w.setWidget(newComponent); - return; - } - } - } - - public boolean requestLayout(Set<Paintable> children) { - // component inside an absolute panel never affects parent nor the - // layout - return true; - } - - public void updateCaption(Paintable component, UIDL uidl) { - AbsoluteWrapper parent2 = (AbsoluteWrapper) ((Widget) component) - .getParent(); - parent2.updateCaption(uidl); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - this.client = client; - // TODO margin handling - if (client.updateComponent(this, uidl, true)) { - rendering = false; - return; - } - - clickEventHandler.handleEventHandlerRegistration(client); - - HashSet<String> unrenderedPids = new HashSet<String>( - pidToComponentWrappper.keySet()); - - for (Iterator<Object> childIterator = uidl.getChildIterator(); childIterator - .hasNext();) { - UIDL cc = (UIDL) childIterator.next(); - if (cc.getTag().equals("cc")) { - UIDL componentUIDL = cc.getChildUIDL(0); - unrenderedPids.remove(componentUIDL.getId()); - getWrapper(client, componentUIDL).updateFromUIDL(cc); - } - } - - for (String pid : unrenderedPids) { - AbsoluteWrapper absoluteWrapper = pidToComponentWrappper.get(pid); - pidToComponentWrappper.remove(pid); - absoluteWrapper.destroy(); - } - rendering = false; - } - - private AbsoluteWrapper getWrapper(ApplicationConnection client, - UIDL componentUIDL) { - AbsoluteWrapper wrapper = pidToComponentWrappper.get(componentUIDL - .getId()); - if (wrapper == null) { - wrapper = new AbsoluteWrapper(client.getPaintable(componentUIDL)); - pidToComponentWrappper.put(componentUIDL.getId(), wrapper); - add(wrapper); - } - return wrapper; - - } - - @Override - public void add(Widget child) { - super.add(child, canvas); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style); - if (previousStyleName == null || !previousStyleName.equals(style)) { - // excessPixelsHorizontal = -1; - // excessPixelsVertical = -1; - } - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - // TODO do this so that canvas gets the sized properly (the area - // inside marginals) - canvas.getStyle().setProperty("width", width); - - if (!rendering) { - if (BrowserInfo.get().isIE6()) { - relayoutWrappersForIe6(); - } - relayoutRelativeChildren(); - } - } - - private void relayoutRelativeChildren() { - for (Widget widget : getChildren()) { - if (widget instanceof AbsoluteWrapper) { - AbsoluteWrapper w = (AbsoluteWrapper) widget; - client.handleComponentRelativeSize(w.getWidget()); - w.updateCaptionPosition(); - } - } - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - // TODO do this so that canvas gets the sized properly (the area - // inside marginals) - canvas.getStyle().setProperty("height", height); - - if (!rendering) { - if (BrowserInfo.get().isIE6()) { - relayoutWrappersForIe6(); - } - relayoutRelativeChildren(); - } - } - - private void relayoutWrappersForIe6() { - for (Widget wrapper : getChildren()) { - if (wrapper instanceof AbsoluteWrapper) { - ((AbsoluteWrapper) wrapper).ie6Layout(); - } - } - } - - public class AbsoluteWrapper extends SimplePanel { - private String css; - private String left; - private String top; - private String right; - private String bottom; - private String zIndex; - - private Paintable paintable; - private VCaption caption; - - public AbsoluteWrapper(Paintable paintable) { - this.paintable = paintable; - setStyleName(CLASSNAME + "-wrapper"); - } - - public void updateCaption(UIDL uidl) { - - boolean captionIsNeeded = VCaption.isNeeded(uidl); - if (captionIsNeeded) { - if (caption == null) { - caption = new VCaption(paintable, client); - VAbsoluteLayout.this.add(caption); - } - caption.updateCaption(uidl); - updateCaptionPosition(); - } else { - if (caption != null) { - caption.removeFromParent(); - caption = null; - } - } - } - - @Override - public void setWidget(Widget w) { - // this fixes #5457 (Widget implementation can change on-the-fly) - paintable = (Paintable) w; - super.setWidget(w); - } - - public void destroy() { - if (caption != null) { - caption.removeFromParent(); - } - client.unregisterPaintable(paintable); - removeFromParent(); - } - - public void updateFromUIDL(UIDL componentUIDL) { - setPosition(componentUIDL.getStringAttribute("css")); - if (getWidget() != paintable) { - setWidget((Widget) paintable); - } - UIDL childUIDL = componentUIDL.getChildUIDL(0); - paintable.updateFromUIDL(childUIDL, client); - if (childUIDL.hasAttribute("cached")) { - // child may need relative size adjustment if wrapper details - // have changed this could be optimized (check if wrapper size - // has changed) - client.handleComponentRelativeSize((Widget) paintable); - } - } - - public void setPosition(String stringAttribute) { - if (css == null || !css.equals(stringAttribute)) { - css = stringAttribute; - top = right = bottom = left = zIndex = null; - if (!css.equals("")) { - String[] properties = css.split(";"); - for (int i = 0; i < properties.length; i++) { - String[] keyValue = properties[i].split(":"); - if (keyValue[0].equals("left")) { - left = keyValue[1]; - } else if (keyValue[0].equals("top")) { - top = keyValue[1]; - } else if (keyValue[0].equals("right")) { - right = keyValue[1]; - } else if (keyValue[0].equals("bottom")) { - bottom = keyValue[1]; - } else if (keyValue[0].equals("z-index")) { - zIndex = keyValue[1]; - } - } - } - // ensure ne values - Style style = getElement().getStyle(); - /* - * IE8 dies when nulling zIndex, even in IE7 mode. All other css - * properties (and even in older IE's) accept null values just - * fine. Assign empty string instead of null. - */ - if (zIndex != null) { - style.setProperty("zIndex", zIndex); - } else { - style.setProperty("zIndex", ""); - } - style.setProperty("top", top); - style.setProperty("left", left); - style.setProperty("right", right); - style.setProperty("bottom", bottom); - - if (BrowserInfo.get().isIE6()) { - ie6Layout(); - } - } - updateCaptionPosition(); - } - - private void updateCaptionPosition() { - if (caption != null) { - Style style = caption.getElement().getStyle(); - style.setProperty("position", "absolute"); - style.setPropertyPx("left", getElement().getOffsetLeft()); - style.setPropertyPx("top", getElement().getOffsetTop() - - caption.getHeight()); - } - } - - private void ie6Layout() { - // special handling for IE6 is needed, it does not support - // setting both left/right or top/bottom - Style style = getElement().getStyle(); - if (bottom != null && top != null) { - // define height for wrapper to simulate bottom property - int bottompixels = measureForIE6(bottom, true); - VConsole.log("ALB" + bottompixels); - int height = canvas.getOffsetHeight() - bottompixels - - getElement().getOffsetTop(); - VConsole.log("ALB" + height); - if (height < 0) { - height = 0; - } - style.setPropertyPx("height", height); - } else { - // reset possibly existing value - style.setProperty("height", ""); - } - if (left != null && right != null) { - // define width for wrapper to simulate right property - int rightPixels = measureForIE6(right, false); - VConsole.log("ALR" + rightPixels); - int width = canvas.getOffsetWidth() - rightPixels - - getElement().getOffsetLeft(); - VConsole.log("ALR" + width); - if (width < 0) { - width = 0; - } - style.setPropertyPx("width", width); - } else { - // reset possibly existing value - style.setProperty("width", ""); - } - } - - } - - private Element measureElement; - - private int measureForIE6(String cssLength, boolean vertical) { - if (measureElement == null) { - measureElement = DOM.createDiv(); - measureElement.getStyle().setProperty("position", "absolute"); - canvas.appendChild(measureElement); - } - if (vertical) { - measureElement.getStyle().setProperty("height", cssLength); - return measureElement.getOffsetHeight(); - } else { - measureElement.getStyle().setProperty("width", cssLength); - return measureElement.getOffsetWidth(); - } - } - - /** - * Returns the deepest nested child component which contains "element". The - * child component is also returned if "element" is part of its caption. - * - * @param element - * An element that is a nested sub element of the root element in - * this layout - * @return The Paintable which the element is a part of. Null if the element - * belongs to the layout and not to a child. - */ - private Paintable getComponent(Element element) { - return Util.getPaintableForElement(client, this, element); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java b/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java deleted file mode 100644 index b1eda728dc..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java +++ /dev/null @@ -1,196 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.InputElement; -import com.google.gwt.dom.client.LabelElement; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.EventHelper; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements - Paintable, Field, FocusHandler, BlurHandler { - - public static final String CLASSNAME = "v-checkbox"; - - String id; - - boolean immediate; - - ApplicationConnection client; - - private Element errorIndicatorElement; - - private Icon icon; - - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; - - public VCheckBox() { - setStyleName(CLASSNAME); - addClickHandler(new ClickHandler() { - - public void onClick(ClickEvent event) { - if (id == null || client == null || !isEnabled()) { - return; - } - - // Add mouse details - MouseEventDetails details = new MouseEventDetails( - event.getNativeEvent(), getElement()); - client.updateVariable(id, "mousedetails", details.serialize(), - false); - client.updateVariable(id, "state", getValue(), immediate); - } - - }); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - Element el = DOM.getFirstChild(getElement()); - while (el != null) { - DOM.sinkEvents(el, - (DOM.getEventsSunk(el) | VTooltip.TOOLTIP_EVENTS)); - el = DOM.getNextSibling(el); - } - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Save details - this.client = client; - id = uidl.getId(); - - // Ensure correct implementation - if (client.updateComponent(this, uidl, false)) { - return; - } - - focusHandlerRegistration = EventHelper.updateFocusHandler(this, client, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, client, - blurHandlerRegistration); - - if (uidl.hasAttribute("error")) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createSpan(); - errorIndicatorElement.setInnerHTML(" "); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - DOM.appendChild(getElement(), errorIndicatorElement); - DOM.sinkEvents(errorIndicatorElement, VTooltip.TOOLTIP_EVENTS - | Event.ONCLICK); - } else { - DOM.setStyleAttribute(errorIndicatorElement, "display", ""); - } - } else if (errorIndicatorElement != null) { - DOM.setStyleAttribute(errorIndicatorElement, "display", "none"); - } - - if (uidl.hasAttribute("readonly")) { - setEnabled(false); - } - - if (uidl.hasAttribute("icon")) { - if (icon == null) { - icon = new Icon(client); - DOM.insertChild(getElement(), icon.getElement(), 1); - icon.sinkEvents(VTooltip.TOOLTIP_EVENTS); - icon.sinkEvents(Event.ONCLICK); - } - icon.setUri(uidl.getStringAttribute("icon")); - } else if (icon != null) { - // detach icon - DOM.removeChild(getElement(), icon.getElement()); - icon = null; - } - - // Set text - setText(uidl.getStringAttribute("caption")); - setValue(uidl.getBooleanVariable("state")); - immediate = uidl.getBooleanAttribute("immediate"); - } - - @Override - public void setText(String text) { - super.setText(text); - if (BrowserInfo.get().isIE() && BrowserInfo.get().getIEVersion() < 8) { - - boolean breakLink = text == null || "".equals(text); - - // break or create link between label element and checkbox, to - // enable native focus outline around checkbox element itself, if - // caption is not present - NodeList<Node> childNodes = getElement().getChildNodes(); - String id = null; - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.getItem(i); - if (item.getNodeName().toLowerCase().equals("input")) { - InputElement input = (InputElement) item; - id = input.getId(); - } - if (item.getNodeName().toLowerCase().equals("label")) { - LabelElement label = (LabelElement) item; - if (breakLink) { - label.setHtmlFor(""); - } else { - label.setHtmlFor(id); - } - } - } - } - } - - @Override - public void onBrowserEvent(Event event) { - if (icon != null && (event.getTypeInt() == Event.ONCLICK) - && (DOM.eventGetTarget(event) == icon.getElement())) { - // Click on icon should do nothing if widget is disabled - if (isEnabled()) { - setValue(!getValue()); - } - } - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - } - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - } - - public void onFocus(FocusEvent arg0) { - client.updateVariable(id, EventId.FOCUS, "", true); - } - - public void onBlur(BlurEvent arg0) { - client.updateVariable(id, EventId.BLUR, "", true); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VContextMenu.java b/src/com/vaadin/terminal/gwt/client/ui/VContextMenu.java index 8158fd90c2..692e13bd94 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VContextMenu.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VContextMenu.java @@ -31,7 +31,6 @@ import com.google.gwt.user.client.ui.MenuBar; import com.google.gwt.user.client.ui.MenuItem; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.impl.FocusImpl; -import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.Focusable; import com.vaadin.terminal.gwt.client.Util; @@ -45,8 +44,8 @@ public class VContextMenu extends VOverlay implements SubPartAware { private int top; - private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor( - 100, new ScheduledCommand() { + private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor(100, + new ScheduledCommand() { public void execute() { imagesLoaded(); } @@ -218,11 +217,6 @@ public class VContextMenu extends VOverlay implements SubPartAware { public void onLoad(LoadEvent event) { // Handle icon onload events to ensure shadow is resized correctly - if (BrowserInfo.get().isIE6()) { - // Ensure PNG transparency works in IE6 - Util.doIE6PngFix((Element) Element.as(event.getNativeEvent() - .getEventTarget())); - } delayedImageLoadExecutioner.trigger(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java deleted file mode 100644 index 585180f8f4..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java +++ /dev/null @@ -1,342 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.dom.client.Style; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.StyleConstants; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.ValueMap; - -public class VCssLayout extends SimplePanel implements Paintable, Container { - public static final String TAGNAME = "csslayout"; - public static final String CLASSNAME = "v-" + TAGNAME; - - private FlowPane panel = new FlowPane(); - - private Element margin = DOM.createDiv(); - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this, EventId.LAYOUT_CLICK) { - - @Override - protected Paintable getChildComponent(Element element) { - return panel.getComponent(element); - } - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - }; - - private boolean hasHeight; - private boolean hasWidth; - private boolean rendering; - - public VCssLayout() { - super(); - getElement().appendChild(margin); - setStyleName(CLASSNAME); - margin.setClassName(CLASSNAME + "-margin"); - setWidget(panel); - } - - @Override - protected Element getContainerElement() { - return margin; - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - // panel.setWidth(width); - hasWidth = width != null && !width.equals(""); - if (!rendering) { - panel.updateRelativeSizes(); - } - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - // panel.setHeight(height); - hasHeight = height != null && !height.equals(""); - if (!rendering) { - panel.updateRelativeSizes(); - } - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - - if (client.updateComponent(this, uidl, true)) { - rendering = false; - return; - } - clickEventHandler.handleEventHandlerRegistration(client); - - final VMarginInfo margins = new VMarginInfo( - uidl.getIntAttribute("margins")); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, - margins.hasTop()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, - margins.hasRight()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, - margins.hasBottom()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, - margins.hasLeft()); - - setStyleName(margin, CLASSNAME + "-" + "spacing", - uidl.hasAttribute("spacing")); - panel.updateFromUIDL(uidl, client); - rendering = false; - } - - public boolean hasChildComponent(Widget component) { - return panel.hasChildComponent(component); - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - panel.replaceChildComponent(oldComponent, newComponent); - } - - public void updateCaption(Paintable component, UIDL uidl) { - panel.updateCaption(component, uidl); - } - - public class FlowPane extends FlowPanel { - - private final HashMap<Widget, VCaption> widgetToCaption = new HashMap<Widget, VCaption>(); - private ApplicationConnection client; - private int lastIndex; - - public FlowPane() { - super(); - setStyleName(CLASSNAME + "-container"); - } - - public void updateRelativeSizes() { - for (Widget w : getChildren()) { - if (w instanceof Paintable) { - client.handleComponentRelativeSize(w); - } - } - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - // for later requests - this.client = client; - - final Collection<Widget> oldWidgets = new HashSet<Widget>(); - for (final Iterator<Widget> iterator = iterator(); iterator - .hasNext();) { - oldWidgets.add(iterator.next()); - } - - ValueMap mapAttribute = null; - if (uidl.hasAttribute("css")) { - mapAttribute = uidl.getMapAttribute("css"); - } - - lastIndex = 0; - for (final Iterator<Object> i = uidl.getChildIterator(); i - .hasNext();) { - final UIDL r = (UIDL) i.next(); - final Paintable child = client.getPaintable(r); - final Widget widget = (Widget) child; - if (widget.getParent() == this) { - oldWidgets.remove(child); - VCaption vCaption = widgetToCaption.get(child); - if (vCaption != null) { - addOrMove(vCaption, lastIndex++); - oldWidgets.remove(vCaption); - } - } - - addOrMove(widget, lastIndex++); - if (mapAttribute != null && mapAttribute.containsKey(r.getId())) { - String css = null; - try { - Style style = widget.getElement().getStyle(); - css = mapAttribute.getString(r.getId()); - String[] cssRules = css.split(";"); - for (int j = 0; j < cssRules.length; j++) { - String[] rule = cssRules[j].split(":"); - if (rule.length == 0) { - continue; - } else { - style.setProperty( - makeCamelCase(rule[0].trim()), - rule[1].trim()); - } - } - } catch (Exception e) { - VConsole.log("CssLayout encounterd invalid css string: " - + css); - } - } - - if (!r.getBooleanAttribute("cached")) { - child.updateFromUIDL(r, client); - } - } - - // loop oldWidgetWrappers that where not re-attached and unregister - // them - for (Widget w : oldWidgets) { - remove(w); - if (w instanceof Paintable) { - final Paintable p = (Paintable) w; - client.unregisterPaintable(p); - } - VCaption vCaption = widgetToCaption.remove(w); - if (vCaption != null) { - remove(vCaption); - } - } - } - - private void addOrMove(Widget child, int index) { - if (child.getParent() == this) { - int currentIndex = getWidgetIndex(child); - if (index == currentIndex) { - return; - } - } - insert(child, index); - } - - public boolean hasChildComponent(Widget component) { - return component.getParent() == this; - } - - public void replaceChildComponent(Widget oldComponent, - Widget newComponent) { - VCaption caption = widgetToCaption.get(oldComponent); - if (caption != null) { - remove(caption); - widgetToCaption.remove(oldComponent); - } - int index = getWidgetIndex(oldComponent); - if (index >= 0) { - remove(oldComponent); - insert(newComponent, index); - } - } - - public void updateCaption(Paintable component, UIDL uidl) { - VCaption caption = widgetToCaption.get(component); - if (VCaption.isNeeded(uidl)) { - Widget widget = (Widget) component; - if (caption == null) { - caption = new VCaption(component, client); - widgetToCaption.put(widget, caption); - insert(caption, getWidgetIndex(widget)); - lastIndex++; - } else if (!caption.isAttached()) { - insert(caption, getWidgetIndex(widget)); - lastIndex++; - } - caption.updateCaption(uidl); - } else if (caption != null) { - remove(caption); - widgetToCaption.remove(component); - } - } - - private Paintable getComponent(Element element) { - return Util - .getPaintableForElement(client, VCssLayout.this, element); - } - - } - - private RenderSpace space; - - public RenderSpace getAllocatedSpace(Widget child) { - if (space == null) { - space = new RenderSpace(-1, -1) { - @Override - public int getWidth() { - if (BrowserInfo.get().isIE()) { - int width = getOffsetWidth(); - int margins = margin.getOffsetWidth() - - panel.getOffsetWidth(); - return width - margins; - } else { - return panel.getOffsetWidth(); - } - } - - @Override - public int getHeight() { - int height = getOffsetHeight(); - int margins = margin.getOffsetHeight() - - panel.getOffsetHeight(); - return height - margins; - } - }; - } - return space; - } - - public boolean requestLayout(Set<Paintable> children) { - if (hasSize()) { - return true; - } else { - // Size may have changed - // TODO optimize this: cache size if not fixed, handle both width - // and height separately - return false; - } - } - - private boolean hasSize() { - return hasWidth && hasHeight; - } - - private static final String makeCamelCase(String cssProperty) { - // TODO this might be cleaner to implement with regexp - while (cssProperty.contains("-")) { - int indexOf = cssProperty.indexOf("-"); - cssProperty = cssProperty.substring(0, indexOf) - + String.valueOf(cssProperty.charAt(indexOf + 1)) - .toUpperCase() + cssProperty.substring(indexOf + 2); - } - if ("float".equals(cssProperty)) { - if (BrowserInfo.get().isIE()) { - return "styleFloat"; - } else { - return "cssFloat"; - } - } - return cssProperty; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java b/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java deleted file mode 100644 index 1fb3f297ad..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java +++ /dev/null @@ -1,168 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Set; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; - -public class VCustomComponent extends SimplePanel implements Container { - - private static final String CLASSNAME = "v-customcomponent"; - private String height; - private ApplicationConnection client; - private boolean rendering; - private String width; - private RenderSpace renderSpace = new RenderSpace(); - - public VCustomComponent() { - super(); - setStyleName(CLASSNAME); - } - - public void updateFromUIDL(UIDL uidl, final ApplicationConnection client) { - rendering = true; - if (client.updateComponent(this, uidl, true)) { - rendering = false; - return; - } - this.client = client; - - final UIDL child = uidl.getChildUIDL(0); - if (child != null) { - final Paintable p = client.getPaintable(child); - if (p != getWidget()) { - if (getWidget() != null) { - client.unregisterPaintable((Paintable) getWidget()); - clear(); - } - setWidget((Widget) p); - } - p.updateFromUIDL(child, client); - } - - boolean updateDynamicSize = updateDynamicSize(); - if (updateDynamicSize) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - // FIXME deferred relative size update needed to fix some - // scrollbar issues in sampler. This must be the wrong way - // to do it. Might be that some other component is broken. - client.handleComponentRelativeSize(VCustomComponent.this); - - } - }); - } - - renderSpace.setWidth(getElement().getOffsetWidth()); - renderSpace.setHeight(getElement().getOffsetHeight()); - - /* - * Needed to update client size if the size of this component has - * changed and the child uses relative size(s). - */ - client.runDescendentsLayout(this); - - rendering = false; - } - - private boolean updateDynamicSize() { - boolean updated = false; - if (isDynamicWidth()) { - int childWidth = Util.getRequiredWidth(getWidget()); - getElement().getStyle().setPropertyPx("width", childWidth); - updated = true; - } - if (isDynamicHeight()) { - int childHeight = Util.getRequiredHeight(getWidget()); - getElement().getStyle().setPropertyPx("height", childHeight); - updated = true; - } - - return updated; - } - - protected boolean isDynamicWidth() { - return width == null || width.equals(""); - } - - protected boolean isDynamicHeight() { - return height == null || height.equals(""); - } - - public boolean hasChildComponent(Widget component) { - if (getWidget() == component) { - return true; - } else { - return false; - } - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - if (hasChildComponent(oldComponent)) { - clear(); - setWidget(newComponent); - } else { - throw new IllegalStateException(); - } - } - - public void updateCaption(Paintable component, UIDL uidl) { - // NOP, custom component dont render composition roots caption - } - - public boolean requestLayout(Set<Paintable> child) { - // If a child grows in size, it will not necessarily be calculated - // correctly unless we remove previous size definitions - if (isDynamicWidth()) { - getElement().getStyle().setProperty("width", ""); - } - if (isDynamicHeight()) { - getElement().getStyle().setProperty("height", ""); - } - - return !updateDynamicSize(); - } - - public RenderSpace getAllocatedSpace(Widget child) { - return renderSpace; - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - renderSpace.setHeight(getElement().getOffsetHeight()); - - if (!height.equals(this.height)) { - this.height = height; - if (!rendering) { - client.handleComponentRelativeSize(getWidget()); - } - } - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - renderSpace.setWidth(getElement().getOffsetWidth()); - - if (!width.equals(this.width)) { - this.width = width; - if (!rendering) { - client.handleComponentRelativeSize(getWidget()); - } - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java b/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java deleted file mode 100644 index 2783db99d1..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java +++ /dev/null @@ -1,453 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.ObjectElement; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VEmbedded extends HTML implements Paintable { - public static final String CLICK_EVENT_IDENTIFIER = "click"; - public static final String ALTERNATE_TEXT = "alt"; - - private static String CLASSNAME = "v-embedded"; - - private String height; - private String width; - private Element browserElement; - - private String type; - - private ApplicationConnection client; - - private final ClickEventHandler clickEventHandler = new ClickEventHandler( - this, CLICK_EVENT_IDENTIFIER) { - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - - }; - - public VEmbedded() { - setStyleName(CLASSNAME); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, true)) { - return; - } - this.client = client; - - boolean clearBrowserElement = true; - - clickEventHandler.handleEventHandlerRegistration(client); - - if (uidl.hasAttribute("type")) { - type = uidl.getStringAttribute("type"); - if (type.equals("image")) { - addStyleName(CLASSNAME + "-image"); - Element el = null; - boolean created = false; - NodeList<Node> nodes = getElement().getChildNodes(); - if (nodes != null && nodes.getLength() == 1) { - Node n = nodes.getItem(0); - if (n.getNodeType() == Node.ELEMENT_NODE) { - Element e = (Element) n; - if (e.getTagName().equals("IMG")) { - el = e; - } - } - } - if (el == null) { - setHTML(""); - el = DOM.createImg(); - created = true; - client.addPngFix(el); - DOM.sinkEvents(el, Event.ONLOAD); - } - - // Set attributes - Style style = el.getStyle(); - String w = uidl.getStringAttribute("width"); - if (w != null) { - style.setProperty("width", w); - } else { - style.setProperty("width", ""); - } - String h = uidl.getStringAttribute("height"); - if (h != null) { - style.setProperty("height", h); - } else { - style.setProperty("height", ""); - } - DOM.setElementProperty(el, "src", getSrc(uidl, client)); - - if (uidl.hasAttribute(ALTERNATE_TEXT)) { - el.setPropertyString(ALTERNATE_TEXT, - uidl.getStringAttribute(ALTERNATE_TEXT)); - } - - if (created) { - // insert in dom late - getElement().appendChild(el); - } - - /* - * Sink tooltip events so tooltip is displayed when hovering the - * image. - */ - sinkEvents(VTooltip.TOOLTIP_EVENTS); - - } else if (type.equals("browser")) { - addStyleName(CLASSNAME + "-browser"); - if (browserElement == null) { - setHTML("<iframe width=\"100%\" height=\"100%\" frameborder=\"0\"" - + " allowTransparency=\"true\" src=\"\"" - + " name=\"" + uidl.getId() + "\"></iframe>"); - browserElement = DOM.getFirstChild(getElement()); - } - DOM.setElementAttribute(browserElement, "src", - getSrc(uidl, client)); - clearBrowserElement = false; - } else { - VConsole.log("Unknown Embedded type '" + type + "'"); - } - } else if (uidl.hasAttribute("mimetype")) { - final String mime = uidl.getStringAttribute("mimetype"); - if (mime.equals("application/x-shockwave-flash")) { - // Handle embedding of Flash - addStyleName(CLASSNAME + "-flash"); - setHTML(createFlashEmbed(uidl)); - } else if (mime.equals("image/svg+xml")) { - addStyleName(CLASSNAME + "-svg"); - String data; - Map<String, String> parameters = getParameters(uidl); - if (parameters.get("data") == null) { - data = getSrc(uidl, client); - } else { - data = "data:image/svg+xml," + parameters.get("data"); - } - setHTML(""); - ObjectElement obj = Document.get().createObjectElement(); - obj.setType(mime); - obj.setData(data); - if (width != null) { - obj.getStyle().setProperty("width", "100%"); - } - if (height != null) { - obj.getStyle().setProperty("height", "100%"); - } - if (uidl.hasAttribute("classid")) { - obj.setAttribute("classid", - uidl.getStringAttribute("classid")); - } - if (uidl.hasAttribute("codebase")) { - obj.setAttribute("codebase", - uidl.getStringAttribute("codebase")); - } - if (uidl.hasAttribute("codetype")) { - obj.setAttribute("codetype", - uidl.getStringAttribute("codetype")); - } - if (uidl.hasAttribute("archive")) { - obj.setAttribute("archive", - uidl.getStringAttribute("archive")); - } - if (uidl.hasAttribute("standby")) { - obj.setAttribute("standby", - uidl.getStringAttribute("standby")); - } - getElement().appendChild(obj); - if (uidl.hasAttribute(ALTERNATE_TEXT)) { - obj.setInnerText(uidl.getStringAttribute(ALTERNATE_TEXT)); - } - } else { - VConsole.log("Unknown Embedded mimetype '" + mime + "'"); - } - } else { - VConsole.log("Unknown Embedded; no type or mimetype attribute"); - } - - if (clearBrowserElement) { - browserElement = null; - } - } - - /** - * Creates the Object and Embed tags for the Flash plugin so it works - * cross-browser - * - * @param uidl - * The UIDL - * @return Tags concatenated into a string - */ - private String createFlashEmbed(UIDL uidl) { - /* - * To ensure cross-browser compatibility we are using the twice-cooked - * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and - * inside it a EMBED for all other browsers. - */ - - StringBuilder html = new StringBuilder(); - - // Start the object tag - html.append("<object "); - - /* - * Add classid required for ActiveX to recognize the flash. This is a - * predefined value which ActiveX recognizes and must be the given - * value. More info can be found on - * http://kb2.adobe.com/cps/415/tn_4150.html. Allow user to override - * this by setting his own classid. - */ - if (uidl.hasAttribute("classid")) { - html.append("classid=\"" - + Util.escapeAttribute(uidl.getStringAttribute("classid")) - + "\" "); - } else { - html.append("classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" "); - } - - /* - * Add codebase required for ActiveX and must be exactly this according - * to http://kb2.adobe.com/cps/415/tn_4150.html to work with the above - * given classid. Again, see more info on - * http://kb2.adobe.com/cps/415/tn_4150.html. Limiting Flash version to - * 6.0.0.0 and above. Allow user to override this by setting his own - * codebase - */ - if (uidl.hasAttribute("codebase")) { - html.append("codebase=\"" - + Util.escapeAttribute(uidl.getStringAttribute("codebase")) - + "\" "); - } else { - html.append("codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0\" "); - } - - // Add width and height - html.append("width=\"" + Util.escapeAttribute(width) + "\" "); - html.append("height=\"" + Util.escapeAttribute(height) + "\" "); - html.append("type=\"application/x-shockwave-flash\" "); - - // Codetype - if (uidl.hasAttribute("codetype")) { - html.append("codetype=\"" - + Util.escapeAttribute(uidl.getStringAttribute("codetype")) - + "\" "); - } - - // Standby - if (uidl.hasAttribute("standby")) { - html.append("standby=\"" - + Util.escapeAttribute(uidl.getStringAttribute("standby")) - + "\" "); - } - - // Archive - if (uidl.hasAttribute("archive")) { - html.append("archive=\"" - + Util.escapeAttribute(uidl.getStringAttribute("archive")) - + "\" "); - } - - // End object tag - html.append(">"); - - // Ensure we have an movie parameter - Map<String, String> parameters = getParameters(uidl); - if (parameters.get("movie") == null) { - parameters.put("movie", getSrc(uidl, client)); - } - - // Add parameters to OBJECT - for (String name : parameters.keySet()) { - html.append("<param "); - html.append("name=\"" + Util.escapeAttribute(name) + "\" "); - html.append("value=\"" + Util.escapeAttribute(parameters.get(name)) - + "\" "); - html.append("/>"); - } - - // Build inner EMBED tag - html.append("<embed "); - html.append("src=\"" + Util.escapeAttribute(getSrc(uidl, client)) - + "\" "); - html.append("width=\"" + Util.escapeAttribute(width) + "\" "); - html.append("height=\"" + Util.escapeAttribute(height) + "\" "); - html.append("type=\"application/x-shockwave-flash\" "); - - // Add the parameters to the Embed - for (String name : parameters.keySet()) { - html.append(Util.escapeAttribute(name)); - html.append("="); - html.append("\"" + Util.escapeAttribute(parameters.get(name)) - + "\""); - } - - // End embed tag - html.append("></embed>"); - - if (uidl.hasAttribute(ALTERNATE_TEXT)) { - html.append(uidl.getStringAttribute(ALTERNATE_TEXT)); - } - - // End object tag - html.append("</object>"); - - return html.toString(); - } - - /** - * Returns a map (name -> value) of all parameters in the UIDL. - * - * @param uidl - * @return - */ - private static Map<String, String> getParameters(UIDL uidl) { - Map<String, String> parameters = new HashMap<String, String>(); - - Iterator<Object> childIterator = uidl.getChildIterator(); - while (childIterator.hasNext()) { - - Object child = childIterator.next(); - if (child instanceof UIDL) { - - UIDL childUIDL = (UIDL) child; - if (childUIDL.getTag().equals("embeddedparam")) { - String name = childUIDL.getStringAttribute("name"); - String value = childUIDL.getStringAttribute("value"); - parameters.put(name, value); - } - } - - } - - return parameters; - } - - /** - * Helper to return translated src-attribute from embedded's UIDL - * - * @param uidl - * @param client - * @return - */ - private String getSrc(UIDL uidl, ApplicationConnection client) { - String url = client.translateVaadinUri(uidl.getStringAttribute("src")); - if (url == null) { - return ""; - } - return url; - } - - @Override - public void setWidth(String width) { - this.width = width; - if (isDynamicHeight()) { - int oldHeight = getOffsetHeight(); - super.setWidth(width); - int newHeight = getOffsetHeight(); - /* - * Must notify parent if the height changes as a result of a width - * change - */ - if (oldHeight != newHeight) { - Util.notifyParentOfSizeChange(this, false); - } - } else { - super.setWidth(width); - } - - } - - private boolean isDynamicWidth() { - return width == null || width.equals(""); - } - - private boolean isDynamicHeight() { - return height == null || height.equals(""); - } - - @Override - public void setHeight(String height) { - this.height = height; - super.setHeight(height); - } - - @Override - protected void onDetach() { - if (BrowserInfo.get().isIE()) { - // Force browser to fire unload event when component is detached - // from the view (IE doesn't do this automatically) - if (browserElement != null) { - /* - * src was previously set to javascript:false, but this was not - * enough to overcome a bug when detaching an iframe with a pdf - * loaded in IE9. about:blank seems to cause the adobe reader - * plugin to unload properly before the iframe is removed. See - * #7855 - */ - DOM.setElementAttribute(browserElement, "src", "about:blank"); - } - } - super.onDetach(); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (DOM.eventGetType(event) == Event.ONLOAD) { - if ("image".equals(type)) { - updateElementDynamicSizeFromImage(); - } - Util.notifyParentOfSizeChange(this, true); - } - - client.handleTooltipEvent(event, this); - } - - /** - * Updates the size of the embedded component's element if size is - * undefined. Without this embeddeds containing images will remain the wrong - * size in certain cases (e.g. #6304). - */ - private void updateElementDynamicSizeFromImage() { - if (isDynamicWidth()) { - getElement().getStyle().setWidth( - getElement().getFirstChildElement().getOffsetWidth(), - Unit.PX); - } - if (isDynamicHeight()) { - getElement().getStyle().setHeight( - getElement().getFirstChildElement().getOffsetHeight(), - Unit.PX); - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/VForm.java deleted file mode 100644 index c0a6e5b275..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VForm.java +++ /dev/null @@ -1,331 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Set; - -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.VErrorMessage; - -public class VForm extends ComplexPanel implements Container, KeyDownHandler { - - protected String id; - - private String height = ""; - - private String width = ""; - - public static final String CLASSNAME = "v-form"; - - private Container lo; - private Element legend = DOM.createLegend(); - private Element caption = DOM.createSpan(); - private Element errorIndicatorElement = DOM.createDiv(); - private Element desc = DOM.createDiv(); - private Icon icon; - private VErrorMessage errorMessage = new VErrorMessage(); - - private Element fieldContainer = DOM.createDiv(); - - private Element footerContainer = DOM.createDiv(); - - private Element fieldSet = DOM.createFieldSet(); - - private Container footer; - - private ApplicationConnection client; - - private RenderInformation renderInformation = new RenderInformation(); - - private int borderPaddingHorizontal = -1; - - private boolean rendering = false; - - ShortcutActionHandler shortcutHandler; - - private HandlerRegistration keyDownRegistration; - - public VForm() { - setElement(DOM.createDiv()); - getElement().appendChild(fieldSet); - setStyleName(CLASSNAME); - fieldSet.appendChild(legend); - legend.appendChild(caption); - errorIndicatorElement.setClassName("v-errorindicator"); - errorIndicatorElement.getStyle().setDisplay(Display.NONE); - errorIndicatorElement.setInnerText(" "); // needed for IE - desc.setClassName("v-form-description"); - fieldSet.appendChild(desc); // Adding description for initial padding - // measurements, removed later if no - // description is set - fieldSet.appendChild(fieldContainer); - errorMessage.setVisible(false); - errorMessage.setStyleName(CLASSNAME + "-errormessage"); - fieldSet.appendChild(errorMessage.getElement()); - fieldSet.appendChild(footerContainer); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - this.client = client; - id = uidl.getId(); - - if (client.updateComponent(this, uidl, false)) { - rendering = false; - return; - } - - boolean legendEmpty = true; - if (uidl.hasAttribute("caption")) { - caption.setInnerText(uidl.getStringAttribute("caption")); - legendEmpty = false; - } else { - caption.setInnerText(""); - } - if (uidl.hasAttribute("icon")) { - if (icon == null) { - icon = new Icon(client); - legend.insertFirst(icon.getElement()); - } - icon.setUri(uidl.getStringAttribute("icon")); - legendEmpty = false; - } else { - if (icon != null) { - legend.removeChild(icon.getElement()); - } - } - if (legendEmpty) { - addStyleDependentName("nocaption"); - } else { - removeStyleDependentName("nocaption"); - } - - if (uidl.hasAttribute("error")) { - final UIDL errorUidl = uidl.getErrors(); - errorMessage.updateFromUIDL(errorUidl); - errorMessage.setVisible(true); - - } else { - errorMessage.setVisible(false); - } - - if (uidl.hasAttribute("description")) { - desc.setInnerHTML(uidl.getStringAttribute("description")); - if (desc.getParentElement() == null) { - fieldSet.insertAfter(desc, legend); - } - } else { - desc.setInnerHTML(""); - if (desc.getParentElement() != null) { - fieldSet.removeChild(desc); - } - } - - updateSize(); - - // first render footer so it will be easier to handle relative height of - // main layout - if (uidl.getChildCount() > 1 - && !uidl.getChildUIDL(1).getTag().equals("actions")) { - // render footer - Container newFooter = (Container) client.getPaintable(uidl - .getChildUIDL(1)); - if (footer == null) { - add((Widget) newFooter, footerContainer); - footer = newFooter; - } else if (newFooter != footer) { - remove((Widget) footer); - client.unregisterPaintable(footer); - add((Widget) newFooter, footerContainer); - } - footer = newFooter; - footer.updateFromUIDL(uidl.getChildUIDL(1), client); - // needed for the main layout to know the space it has available - updateSize(); - } else { - if (footer != null) { - remove((Widget) footer); - client.unregisterPaintable(footer); - // needed for the main layout to know the space it has available - updateSize(); - } - } - - final UIDL layoutUidl = uidl.getChildUIDL(0); - Container newLo = (Container) client.getPaintable(layoutUidl); - if (lo == null) { - lo = newLo; - add((Widget) lo, fieldContainer); - } else if (lo != newLo) { - client.unregisterPaintable(lo); - remove((Widget) lo); - lo = newLo; - add((Widget) lo, fieldContainer); - } - lo.updateFromUIDL(layoutUidl, client); - - // also recalculates size of the footer if undefined size form - see - // #3710 - updateSize(); - client.runDescendentsLayout(this); - - // We may have actions attached - if (uidl.getChildCount() > 1) { - UIDL childUidl = uidl.getChildByTagName("actions"); - if (childUidl != null) { - if (shortcutHandler == null) { - shortcutHandler = new ShortcutActionHandler(id, client); - keyDownRegistration = addDomHandler(this, - KeyDownEvent.getType()); - } - shortcutHandler.updateActionMap(childUidl); - } - } else if (shortcutHandler != null) { - keyDownRegistration.removeHandler(); - shortcutHandler = null; - keyDownRegistration = null; - } - - rendering = false; - } - - public void updateSize() { - - renderInformation.updateSize(getElement()); - - renderInformation.setContentAreaHeight(renderInformation - .getRenderedSize().getHeight() - getSpaceConsumedVertically()); - if (BrowserInfo.get().isIE6()) { - getElement().getStyle().setProperty("overflow", "hidden"); - } - renderInformation.setContentAreaWidth(renderInformation - .getRenderedSize().getWidth() - borderPaddingHorizontal); - } - - public RenderSpace getAllocatedSpace(Widget child) { - if (child == lo) { - return renderInformation.getContentAreaSize(); - } else if (child == footer) { - return new RenderSpace(renderInformation.getContentAreaSize() - .getWidth(), 0); - } else { - VConsole.error("Invalid child requested RenderSpace information"); - return null; - } - } - - public boolean hasChildComponent(Widget component) { - return component != null && (component == lo || component == footer); - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - if (!hasChildComponent(oldComponent)) { - throw new IllegalArgumentException( - "Old component is not inside this Container"); - } - remove(oldComponent); - if (oldComponent == lo) { - lo = (Container) newComponent; - add((Widget) lo, fieldContainer); - } else { - footer = (Container) newComponent; - add((Widget) footer, footerContainer); - } - - } - - public boolean requestLayout(Set<Paintable> child) { - - if (height != null && !"".equals(height) && width != null - && !"".equals(width)) { - /* - * If the height and width has been specified the child components - * cannot make the size of the layout change - */ - - return true; - } - - if (renderInformation.updateSize(getElement())) { - return false; - } else { - return true; - } - - } - - public void updateCaption(Paintable component, UIDL uidl) { - // NOP form don't render caption for neither field layout nor footer - // layout - } - - @Override - public void setHeight(String height) { - if (this.height.equals(height)) { - return; - } - - this.height = height; - super.setHeight(height); - - updateSize(); - } - - /** - * @return pixels consumed by decoration, captions, descrioptiosn etc.. In - * other words space, not used by the actual layout in form. - */ - private int getSpaceConsumedVertically() { - int offsetHeight2 = fieldSet.getOffsetHeight(); - int offsetHeight3 = fieldContainer.getOffsetHeight(); - int borderPadding = offsetHeight2 - offsetHeight3; - return borderPadding; - } - - @Override - public void setWidth(String width) { - if (borderPaddingHorizontal < 0) { - // measure excess size lazily after stylename setting, but before - // setting width - int ow = getOffsetWidth(); - int dow = desc.getOffsetWidth(); - borderPaddingHorizontal = ow - dow; - } - if (Util.equals(this.width, width)) { - return; - } - - this.width = width; - super.setWidth(width); - - updateSize(); - - if (!rendering && height.equals("")) { - // Width might affect height - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this); - } - } - - public void onKeyDown(KeyDownEvent event) { - shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent())); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java deleted file mode 100644 index 174e66b7aa..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java +++ /dev/null @@ -1,533 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.FlexTable; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.StyleConstants; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -/** - * Two col Layout that places caption on left col and field on right col - */ -public class VFormLayout extends SimplePanel implements Container { - - private final static String CLASSNAME = "v-formlayout"; - - private ApplicationConnection client; - private VFormLayoutTable table; - - private String width = ""; - private String height = ""; - - private boolean rendering = false; - - public VFormLayout() { - super(); - setStyleName(CLASSNAME); - table = new VFormLayoutTable(); - setWidget(table); - } - - /** - * Parses the stylenames from an uidl - * - * @param uidl - * The uidl to get the stylenames from - * @return An array of stylenames - */ - private String[] getStylesFromUIDL(UIDL uidl) { - List<String> styles = new ArrayList<String>(); - if (uidl.hasAttribute("style")) { - String[] stylesnames = uidl.getStringAttribute("style").split(" "); - for (String name : stylesnames) { - styles.add(name); - } - } - - if (uidl.hasAttribute("disabled")) { - styles.add(ApplicationConnection.DISABLED_CLASSNAME); - } - - return styles.toArray(new String[styles.size()]); - } - - public class VFormLayoutTable extends FlexTable implements ClickHandler { - - private static final int COLUMN_CAPTION = 0; - private static final int COLUMN_ERRORFLAG = 1; - private static final int COLUMN_WIDGET = 2; - - private HashMap<Paintable, Caption> componentToCaption = new HashMap<Paintable, Caption>(); - private HashMap<Paintable, ErrorFlag> componentToError = new HashMap<Paintable, ErrorFlag>(); - - public VFormLayoutTable() { - DOM.setElementProperty(getElement(), "cellPadding", "0"); - DOM.setElementProperty(getElement(), "cellSpacing", "0"); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - final VMarginInfo margins = new VMarginInfo( - uidl.getIntAttribute("margins")); - - Element margin = getElement(); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, - margins.hasTop()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, - margins.hasRight()); - setStyleName(margin, - CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, - margins.hasBottom()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, - margins.hasLeft()); - - setStyleName(margin, CLASSNAME + "-" + "spacing", - uidl.hasAttribute("spacing")); - - int i = 0; - for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext(); i++) { - prepareCell(i, 1); - final UIDL childUidl = (UIDL) it.next(); - final Paintable p = client.getPaintable(childUidl); - Caption caption = componentToCaption.get(p); - if (caption == null) { - caption = new Caption(p, client); - caption.addClickHandler(this); - componentToCaption.put(p, caption); - } - ErrorFlag error = componentToError.get(p); - if (error == null) { - error = new ErrorFlag(); - componentToError.put(p, error); - } - prepareCell(i, COLUMN_WIDGET); - final Paintable oldComponent = (Paintable) getWidget(i, - COLUMN_WIDGET); - if (oldComponent == null) { - setWidget(i, COLUMN_WIDGET, (Widget) p); - } else if (oldComponent != p) { - client.unregisterPaintable(oldComponent); - setWidget(i, COLUMN_WIDGET, (Widget) p); - } - getCellFormatter().setStyleName(i, COLUMN_WIDGET, - CLASSNAME + "-contentcell"); - getCellFormatter().setStyleName(i, COLUMN_CAPTION, - CLASSNAME + "-captioncell"); - setWidget(i, COLUMN_CAPTION, caption); - - setContentWidth(i); - - getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG, - CLASSNAME + "-errorcell"); - setWidget(i, COLUMN_ERRORFLAG, error); - - p.updateFromUIDL(childUidl, client); - - String rowstyles = CLASSNAME + "-row"; - if (i == 0) { - rowstyles += " " + CLASSNAME + "-firstrow"; - } - if (!it.hasNext()) { - rowstyles += " " + CLASSNAME + "-lastrow"; - } - - getRowFormatter().setStyleName(i, rowstyles); - - } - - while (getRowCount() > i) { - final Paintable p = (Paintable) getWidget(i, COLUMN_WIDGET); - client.unregisterPaintable(p); - componentToCaption.remove(p); - removeRow(i); - } - - /* - * Must update relative sized fields last when it is clear how much - * space they are allowed to use - */ - for (Paintable p : componentToCaption.keySet()) { - client.handleComponentRelativeSize((Widget) p); - } - } - - public void setContentWidths() { - for (int row = 0; row < getRowCount(); row++) { - setContentWidth(row); - } - } - - private void setContentWidth(int row) { - String width = ""; - if (!isDynamicWidth()) { - width = "100%"; - } - getCellFormatter().setWidth(row, COLUMN_WIDGET, width); - } - - public void replaceChildComponent(Widget oldComponent, - Widget newComponent) { - int i; - for (i = 0; i < getRowCount(); i++) { - Widget candidate = getWidget(i, COLUMN_WIDGET); - if (oldComponent == candidate) { - Caption oldCap = componentToCaption.get(oldComponent); - final Caption newCap = new Caption( - (Paintable) newComponent, client); - newCap.addClickHandler(this); - newCap.setStyleName(oldCap.getStyleName()); - componentToCaption.put((Paintable) newComponent, newCap); - ErrorFlag error = componentToError.get(newComponent); - if (error == null) { - error = new ErrorFlag(); - componentToError.put((Paintable) newComponent, error); - } - - setWidget(i, COLUMN_CAPTION, newCap); - setWidget(i, COLUMN_ERRORFLAG, error); - setWidget(i, COLUMN_WIDGET, newComponent); - break; - } - } - - } - - public boolean hasChildComponent(Widget component) { - return componentToCaption.containsKey(component); - } - - public void updateCaption(Paintable component, UIDL uidl) { - final Caption c = componentToCaption.get(component); - if (c != null) { - c.updateCaption(uidl); - } - final ErrorFlag e = componentToError.get(component); - if (e != null) { - e.updateFromUIDL(uidl, component); - } - - } - - public int getAllocatedWidth(Widget child, int availableWidth) { - Caption caption = componentToCaption.get(child); - ErrorFlag error = componentToError.get(child); - int width = availableWidth; - - if (caption != null) { - width -= DOM.getParent(caption.getElement()).getOffsetWidth(); - } - if (error != null) { - width -= DOM.getParent(error.getElement()).getOffsetWidth(); - } - - return width; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt - * .event.dom.client.ClickEvent) - */ - public void onClick(ClickEvent event) { - Caption caption = (Caption) event.getSource(); - if (caption.getOwner() != null) { - if (caption.getOwner() instanceof Focusable) { - ((Focusable) caption.getOwner()).focus(); - } else if (caption.getOwner() instanceof com.google.gwt.user.client.ui.Focusable) { - ((com.google.gwt.user.client.ui.Focusable) caption - .getOwner()).setFocus(true); - } - } - } - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - - this.client = client; - - if (client.updateComponent(this, uidl, true)) { - rendering = false; - return; - } - - table.updateFromUIDL(uidl, client); - - rendering = false; - } - - public boolean isDynamicWidth() { - return width.equals(""); - } - - public boolean hasChildComponent(Widget component) { - return table.hasChildComponent(component); - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - table.replaceChildComponent(oldComponent, newComponent); - } - - public void updateCaption(Paintable component, UIDL uidl) { - table.updateCaption(component, uidl); - } - - public class Caption extends HTML { - - public static final String CLASSNAME = "v-caption"; - - private final Paintable owner; - - private Element requiredFieldIndicator; - - private Icon icon; - - private Element captionText; - - private final ApplicationConnection client; - - /** - * - * @param component - * optional owner of caption. If not set, getOwner will - * return null - * @param client - */ - public Caption(Paintable component, ApplicationConnection client) { - super(); - this.client = client; - owner = component; - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - private void setStyles(String[] styles) { - String styleName = CLASSNAME; - - if (styles != null) { - for (String style : styles) { - if (ApplicationConnection.DISABLED_CLASSNAME.equals(style)) { - // Add v-disabled also without classname prefix so - // generic v-disabled CSS rules work - styleName += " " + style; - } - - styleName += " " + CLASSNAME + "-" + style; - } - } - - setStyleName(styleName); - } - - public void updateCaption(UIDL uidl) { - setVisible(!uidl.getBooleanAttribute("invisible")); - - // Update styles as they might have changed when the caption changed - setStyles(getStylesFromUIDL(uidl)); - - boolean isEmpty = true; - - if (uidl.hasAttribute("icon")) { - if (icon == null) { - icon = new Icon(client); - - DOM.insertChild(getElement(), icon.getElement(), 0); - } - icon.setUri(uidl.getStringAttribute("icon")); - isEmpty = false; - } else { - if (icon != null) { - DOM.removeChild(getElement(), icon.getElement()); - icon = null; - } - - } - - if (uidl.hasAttribute("caption")) { - if (captionText == null) { - captionText = DOM.createSpan(); - DOM.insertChild(getElement(), captionText, icon == null ? 0 - : 1); - } - String c = uidl.getStringAttribute("caption"); - if (c == null) { - c = ""; - } else { - isEmpty = false; - } - DOM.setInnerText(captionText, c); - } else { - // TODO should span also be removed - } - - if (uidl.hasAttribute("description")) { - if (captionText != null) { - addStyleDependentName("hasdescription"); - } else { - removeStyleDependentName("hasdescription"); - } - } - - if (uidl.getBooleanAttribute("required")) { - if (requiredFieldIndicator == null) { - requiredFieldIndicator = DOM.createSpan(); - DOM.setInnerText(requiredFieldIndicator, "*"); - DOM.setElementProperty(requiredFieldIndicator, "className", - "v-required-field-indicator"); - DOM.appendChild(getElement(), requiredFieldIndicator); - } - } else { - if (requiredFieldIndicator != null) { - DOM.removeChild(getElement(), requiredFieldIndicator); - requiredFieldIndicator = null; - } - } - - // Workaround for IE weirdness, sometimes returns bad height in some - // circumstances when Caption is empty. See #1444 - // IE7 bugs more often. I wonder what happens when IE8 arrives... - if (BrowserInfo.get().isIE()) { - if (isEmpty) { - setHeight("0px"); - DOM.setStyleAttribute(getElement(), "overflow", "hidden"); - } else { - setHeight(""); - DOM.setStyleAttribute(getElement(), "overflow", ""); - } - - } - - } - - /** - * Returns Paintable for which this Caption belongs to. - * - * @return owner Widget - */ - public Paintable getOwner() { - return owner; - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, owner); - } - } - } - - private class ErrorFlag extends HTML { - private static final String CLASSNAME = VFormLayout.CLASSNAME - + "-error-indicator"; - Element errorIndicatorElement; - private Paintable owner; - - public ErrorFlag() { - setStyleName(CLASSNAME); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - public void updateFromUIDL(UIDL uidl, Paintable component) { - owner = component; - if (uidl.hasAttribute("error") - && !uidl.getBooleanAttribute("hideErrors")) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createDiv(); - DOM.setInnerHTML(errorIndicatorElement, " "); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - DOM.appendChild(getElement(), errorIndicatorElement); - } - - } else if (errorIndicatorElement != null) { - DOM.removeChild(getElement(), errorIndicatorElement); - errorIndicatorElement = null; - } - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (owner != null) { - client.handleTooltipEvent(event, owner); - } - } - - } - - public boolean requestLayout(Set<Paintable> child) { - if (height.equals("") || width.equals("")) { - // A dynamic size might change due to children changes - return false; - } - - return true; - } - - public RenderSpace getAllocatedSpace(Widget child) { - int width = 0; - int height = 0; - - if (!this.width.equals("")) { - int availableWidth = getOffsetWidth(); - width = table.getAllocatedWidth(child, availableWidth); - } - - return new RenderSpace(width, height, false); - } - - @Override - public void setHeight(String height) { - if (this.height.equals(height)) { - return; - } - - this.height = height; - super.setHeight(height); - } - - @Override - public void setWidth(String width) { - if (this.width.equals(width)) { - return; - } - - this.width = width; - super.setWidth(width); - - if (!rendering) { - table.setContentWidths(); - if (height.equals("")) { - // Width might affect height - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this); - } - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java deleted file mode 100644 index 82b3eabf40..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java +++ /dev/null @@ -1,1132 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.AbsolutePanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.StyleConstants; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.layout.CellBasedLayout; -import com.vaadin.terminal.gwt.client.ui.layout.ChildComponentContainer; - -public class VGridLayout extends SimplePanel implements Paintable, Container { - - public static final String CLASSNAME = "v-gridlayout"; - - private DivElement margin = Document.get().createDivElement(); - - private final AbsolutePanel canvas = new AbsolutePanel(); - - private ApplicationConnection client; - - protected HashMap<Widget, ChildComponentContainer> widgetToComponentContainer = new HashMap<Widget, ChildComponentContainer>(); - - private HashMap<Paintable, Cell> paintableToCell = new HashMap<Paintable, Cell>(); - - private int spacingPixelsHorizontal; - private int spacingPixelsVertical; - - private int[] columnWidths; - private int[] rowHeights; - - private String height; - - private String width; - - private int[] colExpandRatioArray; - - private int[] rowExpandRatioArray; - - private int[] minColumnWidths; - - private int[] minRowHeights; - - private boolean rendering; - - private HashMap<Widget, ChildComponentContainer> nonRenderedWidgets; - - private boolean sizeChangedDuringRendering = false; - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this, EventId.LAYOUT_CLICK) { - - @Override - protected Paintable getChildComponent(Element element) { - return getComponent(element); - } - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - }; - - public VGridLayout() { - super(); - getElement().appendChild(margin); - setStyleName(CLASSNAME); - setWidget(canvas); - } - - @Override - protected Element getContainerElement() { - return margin.cast(); - } - - /** - * Returns the column widths measured in pixels - * - * @return - */ - protected int[] getColumnWidths() { - return columnWidths; - } - - /** - * Returns the row heights measured in pixels - * - * @return - */ - protected int[] getRowHeights() { - return rowHeights; - } - - /** - * Returns the spacing between the cells horizontally in pixels - * - * @return - */ - protected int getHorizontalSpacing() { - return spacingPixelsHorizontal; - } - - /** - * Returns the spacing between the cells vertically in pixels - * - * @return - */ - protected int getVerticalSpacing() { - return spacingPixelsVertical; - } - - @SuppressWarnings("unchecked") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - this.client = client; - - if (client.updateComponent(this, uidl, true)) { - rendering = false; - return; - } - clickEventHandler.handleEventHandlerRegistration(client); - - canvas.setWidth("0px"); - - handleMargins(uidl); - detectSpacing(uidl); - - int cols = uidl.getIntAttribute("w"); - int rows = uidl.getIntAttribute("h"); - - columnWidths = new int[cols]; - rowHeights = new int[rows]; - - if (cells == null) { - cells = new Cell[cols][rows]; - } else if (cells.length != cols || cells[0].length != rows) { - Cell[][] newCells = new Cell[cols][rows]; - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - if (i < cols && j < rows) { - newCells[i][j] = cells[i][j]; - } - } - } - cells = newCells; - } - - nonRenderedWidgets = (HashMap<Widget, ChildComponentContainer>) widgetToComponentContainer - .clone(); - - final int[] alignments = uidl.getIntArrayAttribute("alignments"); - int alignmentIndex = 0; - - LinkedList<Cell> pendingCells = new LinkedList<Cell>(); - - LinkedList<Cell> relativeHeighted = new LinkedList<Cell>(); - - for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) { - final UIDL r = (UIDL) i.next(); - if ("gr".equals(r.getTag())) { - for (final Iterator<?> j = r.getChildIterator(); j.hasNext();) { - final UIDL c = (UIDL) j.next(); - if ("gc".equals(c.getTag())) { - Cell cell = getCell(c); - if (cell.hasContent()) { - boolean rendered = cell.renderIfNoRelativeWidth(); - cell.alignment = alignments[alignmentIndex++]; - if (!rendered) { - pendingCells.add(cell); - } - - if (cell.colspan > 1) { - storeColSpannedCell(cell); - } else if (rendered) { - // strore non-colspanned widths to columnWidth - // array - if (columnWidths[cell.col] < cell.getWidth()) { - columnWidths[cell.col] = cell.getWidth(); - } - } - if (cell.hasRelativeHeight()) { - relativeHeighted.add(cell); - } - } - } - } - } - } - - colExpandRatioArray = uidl.getIntArrayAttribute("colExpand"); - rowExpandRatioArray = uidl.getIntArrayAttribute("rowExpand"); - distributeColSpanWidths(); - - minColumnWidths = cloneArray(columnWidths); - expandColumns(); - - renderRemainingComponentsWithNoRelativeHeight(pendingCells); - - detectRowHeights(); - - expandRows(); - - renderRemainingComponents(pendingCells); - - for (Cell cell : relativeHeighted) { - // rendering done above so cell.cc should not be null - Widget widget2 = cell.cc.getWidget(); - client.handleComponentRelativeSize(widget2); - cell.cc.updateWidgetSize(); - } - - layoutCells(); - - // clean non rendered components - for (Widget w : nonRenderedWidgets.keySet()) { - ChildComponentContainer childComponentContainer = widgetToComponentContainer - .get(w); - paintableToCell.remove(w); - widgetToComponentContainer.remove(w); - childComponentContainer.removeFromParent(); - client.unregisterPaintable((Paintable) w); - } - nonRenderedWidgets = null; - - rendering = false; - sizeChangedDuringRendering = false; - - } - - private static int[] cloneArray(int[] toBeCloned) { - int[] clone = new int[toBeCloned.length]; - for (int i = 0; i < clone.length; i++) { - clone[i] = toBeCloned[i] * 1; - } - return clone; - } - - private void expandRows() { - if (!"".equals(height)) { - int usedSpace = minRowHeights[0]; - for (int i = 1; i < minRowHeights.length; i++) { - usedSpace += spacingPixelsVertical + minRowHeights[i]; - } - int availableSpace = getOffsetHeight() - marginTopAndBottom; - int excessSpace = availableSpace - usedSpace; - int distributed = 0; - if (excessSpace > 0) { - for (int i = 0; i < rowHeights.length; i++) { - int ew = excessSpace * rowExpandRatioArray[i] / 1000; - rowHeights[i] = minRowHeights[i] + ew; - distributed += ew; - } - excessSpace -= distributed; - int c = 0; - while (excessSpace > 0) { - rowHeights[c % rowHeights.length]++; - excessSpace--; - c++; - } - } - } - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - if (!height.equals(this.height)) { - this.height = height; - if (rendering) { - sizeChangedDuringRendering = true; - } else { - expandRows(); - layoutCells(); - for (Paintable c : paintableToCell.keySet()) { - client.handleComponentRelativeSize((Widget) c); - } - } - } - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - if (!width.equals(this.width)) { - this.width = width; - if (rendering) { - sizeChangedDuringRendering = true; - } else { - int[] oldWidths = cloneArray(columnWidths); - expandColumns(); - boolean heightChanged = false; - HashSet<Integer> dirtyRows = null; - for (int i = 0; i < oldWidths.length; i++) { - if (columnWidths[i] != oldWidths[i]) { - Cell[] column = cells[i]; - for (int j = 0; j < column.length; j++) { - Cell c = column[j]; - if (c != null && c.cc != null - && c.widthCanAffectHeight()) { - c.cc.setContainerSize(c.getAvailableWidth(), - c.getAvailableHeight()); - client.handleComponentRelativeSize(c.cc - .getWidget()); - c.cc.updateWidgetSize(); - int newHeight = c.getHeight(); - if (columnWidths[i] < oldWidths[i] - && newHeight > minRowHeights[j] - && c.rowspan == 1) { - /* - * The width of this column was reduced and - * this affected the height. The height is - * now greater than the previously - * calculated minHeight for the row. - */ - minRowHeights[j] = newHeight; - if (newHeight > rowHeights[j]) { - /* - * The new height is greater than the - * previously calculated rowHeight -> we - * need to recalculate heights later on - */ - rowHeights[j] = newHeight; - heightChanged = true; - } - } else if (newHeight < minRowHeights[j]) { - /* - * The new height of the component is less - * than the previously calculated min row - * height. The min row height may be - * affected and must thus be recalculated - */ - if (dirtyRows == null) { - dirtyRows = new HashSet<Integer>(); - } - dirtyRows.add(j); - } - } - } - } - } - if (dirtyRows != null) { - /* flag indicating that there is a potential row shrinking */ - boolean rowMayShrink = false; - for (Integer rowIndex : dirtyRows) { - int oldMinimum = minRowHeights[rowIndex]; - int newMinimum = 0; - for (int colIndex = 0; colIndex < columnWidths.length; colIndex++) { - Cell cell = cells[colIndex][rowIndex]; - if (cell != null && !cell.hasRelativeHeight() - && cell.getHeight() > newMinimum) { - newMinimum = cell.getHeight(); - } - } - if (newMinimum < oldMinimum) { - minRowHeights[rowIndex] = rowHeights[rowIndex] = newMinimum; - rowMayShrink = true; - } - } - if (rowMayShrink) { - distributeRowSpanHeights(); - minRowHeights = cloneArray(rowHeights); - heightChanged = true; - } - - } - layoutCells(); - for (Paintable c : paintableToCell.keySet()) { - client.handleComponentRelativeSize((Widget) c); - } - if (heightChanged && "".equals(height)) { - Util.notifyParentOfSizeChange(this, false); - } - } - } - } - - private void expandColumns() { - if (!"".equals(width)) { - int usedSpace = minColumnWidths[0]; - for (int i = 1; i < minColumnWidths.length; i++) { - usedSpace += spacingPixelsHorizontal + minColumnWidths[i]; - } - canvas.setWidth(""); - int availableSpace = canvas.getOffsetWidth(); - int excessSpace = availableSpace - usedSpace; - int distributed = 0; - if (excessSpace > 0) { - for (int i = 0; i < columnWidths.length; i++) { - int ew = excessSpace * colExpandRatioArray[i] / 1000; - columnWidths[i] = minColumnWidths[i] + ew; - distributed += ew; - } - excessSpace -= distributed; - int c = 0; - while (excessSpace > 0) { - columnWidths[c % columnWidths.length]++; - excessSpace--; - c++; - } - } - } - } - - private void layoutCells() { - int x = 0; - int y = 0; - for (int i = 0; i < cells.length; i++) { - y = 0; - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - cell.layout(x, y); - } - y += rowHeights[j] + spacingPixelsVertical; - } - x += columnWidths[i] + spacingPixelsHorizontal; - } - - if (isUndefinedWidth()) { - canvas.setWidth((x - spacingPixelsHorizontal) + "px"); - } else { - // main element defines width - canvas.setWidth(""); - } - - int canvasHeight; - if (isUndefinedHeight()) { - canvasHeight = y - spacingPixelsVertical; - } else { - canvasHeight = getOffsetHeight() - marginTopAndBottom; - if (canvasHeight < 0) { - canvasHeight = 0; - } - } - canvas.setHeight(canvasHeight + "px"); - } - - private boolean isUndefinedHeight() { - return "".equals(height); - } - - private boolean isUndefinedWidth() { - return "".equals(width); - } - - private void renderRemainingComponents(LinkedList<Cell> pendingCells) { - for (Cell cell : pendingCells) { - cell.render(); - } - } - - private void detectRowHeights() { - - // collect min rowheight from non-rowspanned cells - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - /* - * Setting fixing container width may in some situations - * affect height. Example: Label with wrapping text without - * or with relative width. - */ - if (cell.cc != null && cell.widthCanAffectHeight()) { - cell.cc.setWidth(cell.getAvailableWidth() + "px"); - cell.cc.updateWidgetSize(); - } - if (cell.rowspan == 1) { - if (!cell.hasRelativeHeight() - && rowHeights[j] < cell.getHeight()) { - rowHeights[j] = cell.getHeight(); - } - } else { - storeRowSpannedCell(cell); - } - } - } - } - - distributeRowSpanHeights(); - - minRowHeights = cloneArray(rowHeights); - } - - private void storeRowSpannedCell(Cell cell) { - SpanList l = null; - for (SpanList list : rowSpans) { - if (list.span < cell.rowspan) { - continue; - } else { - // insert before this - l = list; - break; - } - } - if (l == null) { - l = new SpanList(cell.rowspan); - rowSpans.add(l); - } else if (l.span != cell.rowspan) { - SpanList newL = new SpanList(cell.rowspan); - rowSpans.add(rowSpans.indexOf(l), newL); - l = newL; - } - l.cells.add(cell); - } - - private void renderRemainingComponentsWithNoRelativeHeight( - LinkedList<Cell> pendingCells) { - - for (Iterator<Cell> iterator = pendingCells.iterator(); iterator - .hasNext();) { - Cell cell = iterator.next(); - if (!cell.hasRelativeHeight()) { - cell.render(); - iterator.remove(); - } - } - - } - - /** - * Iterates colspanned cells, ensures cols have enough space to accommodate - * them - */ - private void distributeColSpanWidths() { - for (SpanList list : colSpans) { - for (Cell cell : list.cells) { - // cells with relative content may return non 0 here if on - // subsequent renders - int width = cell.hasRelativeWidth() ? 0 : cell.getWidth(); - distributeSpanSize(columnWidths, cell.col, cell.colspan, - spacingPixelsHorizontal, width, colExpandRatioArray); - } - } - } - - /** - * Iterates rowspanned cells, ensures rows have enough space to accommodate - * them - */ - private void distributeRowSpanHeights() { - for (SpanList list : rowSpans) { - for (Cell cell : list.cells) { - // cells with relative content may return non 0 here if on - // subsequent renders - int height = cell.hasRelativeHeight() ? 0 : cell.getHeight(); - distributeSpanSize(rowHeights, cell.row, cell.rowspan, - spacingPixelsVertical, height, rowExpandRatioArray); - } - } - } - - private static void distributeSpanSize(int[] dimensions, - int spanStartIndex, int spanSize, int spacingSize, int size, - int[] expansionRatios) { - int allocated = dimensions[spanStartIndex]; - for (int i = 1; i < spanSize; i++) { - allocated += spacingSize + dimensions[spanStartIndex + i]; - } - if (allocated < size) { - // dimensions needs to be expanded due spanned cell - int neededExtraSpace = size - allocated; - int allocatedExtraSpace = 0; - - // Divide space according to expansion ratios if any span has a - // ratio - int totalExpansion = 0; - for (int i = 0; i < spanSize; i++) { - int itemIndex = spanStartIndex + i; - totalExpansion += expansionRatios[itemIndex]; - } - - for (int i = 0; i < spanSize; i++) { - int itemIndex = spanStartIndex + i; - int expansion; - if (totalExpansion == 0) { - // Divide equally among all cells if there are no - // expansion ratios - expansion = neededExtraSpace / spanSize; - } else { - expansion = neededExtraSpace * expansionRatios[itemIndex] - / totalExpansion; - } - dimensions[itemIndex] += expansion; - allocatedExtraSpace += expansion; - } - - // We might still miss a couple of pixels because of - // rounding errors... - if (neededExtraSpace > allocatedExtraSpace) { - for (int i = 0; i < spanSize; i++) { - // Add one pixel to every cell until we have - // compensated for any rounding error - int itemIndex = spanStartIndex + i; - dimensions[itemIndex] += 1; - allocatedExtraSpace += 1; - if (neededExtraSpace == allocatedExtraSpace) { - break; - } - } - } - } - } - - private LinkedList<SpanList> colSpans = new LinkedList<SpanList>(); - private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>(); - - private int marginTopAndBottom; - - private class SpanList { - final int span; - List<Cell> cells = new LinkedList<Cell>(); - - public SpanList(int span) { - this.span = span; - } - } - - private void storeColSpannedCell(Cell cell) { - SpanList l = null; - for (SpanList list : colSpans) { - if (list.span < cell.colspan) { - continue; - } else { - // insert before this - l = list; - break; - } - } - if (l == null) { - l = new SpanList(cell.colspan); - colSpans.add(l); - } else if (l.span != cell.colspan) { - - SpanList newL = new SpanList(cell.colspan); - colSpans.add(colSpans.indexOf(l), newL); - l = newL; - } - l.cells.add(cell); - } - - private void detectSpacing(UIDL uidl) { - DivElement spacingmeter = Document.get().createDivElement(); - spacingmeter.setClassName(CLASSNAME + "-" + "spacing-" - + (uidl.getBooleanAttribute("spacing") ? "on" : "off")); - spacingmeter.getStyle().setProperty("width", "0"); - spacingmeter.getStyle().setProperty("height", "0"); - canvas.getElement().appendChild(spacingmeter); - spacingPixelsHorizontal = spacingmeter.getOffsetWidth(); - spacingPixelsVertical = spacingmeter.getOffsetHeight(); - canvas.getElement().removeChild(spacingmeter); - } - - private void handleMargins(UIDL uidl) { - final VMarginInfo margins = new VMarginInfo( - uidl.getIntAttribute("margins")); - - String styles = CLASSNAME + "-margin"; - if (margins.hasTop()) { - styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_TOP; - } - if (margins.hasRight()) { - styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT; - } - if (margins.hasBottom()) { - styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM; - } - if (margins.hasLeft()) { - styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_LEFT; - } - margin.setClassName(styles); - - marginTopAndBottom = margin.getOffsetHeight() - - canvas.getOffsetHeight(); - } - - public boolean hasChildComponent(Widget component) { - return paintableToCell.containsKey(component); - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - ChildComponentContainer componentContainer = widgetToComponentContainer - .remove(oldComponent); - if (componentContainer == null) { - return; - } - - componentContainer.setWidget(newComponent); - widgetToComponentContainer.put(newComponent, componentContainer); - - paintableToCell.put((Paintable) newComponent, - paintableToCell.get(oldComponent)); - } - - public void updateCaption(Paintable component, UIDL uidl) { - ChildComponentContainer cc = widgetToComponentContainer.get(component); - if (cc != null) { - cc.updateCaption(uidl, client); - } - if (!rendering) { - // ensure rel size details are updated - paintableToCell.get(component).updateRelSizeStatus(uidl); - /* - * This was a component-only update and the possible size change - * must be propagated to the layout - */ - client.captionSizeUpdated(component); - } - } - - public boolean requestLayout(final Set<Paintable> changedChildren) { - boolean needsLayout = false; - boolean reDistributeColSpanWidths = false; - boolean reDistributeRowSpanHeights = false; - int offsetHeight = canvas.getOffsetHeight(); - int offsetWidth = canvas.getOffsetWidth(); - if ("".equals(width) || "".equals(height)) { - needsLayout = true; - } - ArrayList<Integer> dirtyColumns = new ArrayList<Integer>(); - ArrayList<Integer> dirtyRows = new ArrayList<Integer>(); - for (Paintable paintable : changedChildren) { - - Cell cell = paintableToCell.get(paintable); - if (!cell.hasRelativeHeight() || !cell.hasRelativeWidth()) { - // cell sizes will only stay still if only relatively - // sized components - // check if changed child affects min col widths - assert cell.cc != null; - cell.cc.setWidth(""); - cell.cc.setHeight(""); - - cell.cc.updateWidgetSize(); - - /* - * If this is the result of an caption icon onload event the - * caption size may have changed - */ - cell.cc.updateCaptionSize(); - - int width = cell.getWidth(); - int allocated = columnWidths[cell.col]; - for (int i = 1; i < cell.colspan; i++) { - allocated += spacingPixelsHorizontal - + columnWidths[cell.col + i]; - } - if (allocated < width) { - needsLayout = true; - if (cell.colspan == 1) { - // do simple column width expansion - columnWidths[cell.col] = minColumnWidths[cell.col] = width; - } else { - // mark that col span expansion is needed - reDistributeColSpanWidths = true; - } - } else if (allocated != width) { - // size is smaller thant allocated, column might - // shrink - dirtyColumns.add(cell.col); - } - - int height = cell.getHeight(); - - allocated = rowHeights[cell.row]; - for (int i = 1; i < cell.rowspan; i++) { - allocated += spacingPixelsVertical - + rowHeights[cell.row + i]; - } - if (allocated < height) { - needsLayout = true; - if (cell.rowspan == 1) { - // do simple row expansion - rowHeights[cell.row] = minRowHeights[cell.row] = height; - } else { - // mark that row span expansion is needed - reDistributeRowSpanHeights = true; - } - } else if (allocated != height) { - // size is smaller than allocated, row might shrink - dirtyRows.add(cell.row); - } - } - } - - if (dirtyColumns.size() > 0) { - for (Integer colIndex : dirtyColumns) { - int colW = 0; - for (int i = 0; i < rowHeights.length; i++) { - Cell cell = cells[colIndex][i]; - if (cell != null && cell.getChildUIDL() != null - && !cell.hasRelativeWidth() && cell.colspan == 1) { - int width = cell.getWidth(); - if (width > colW) { - colW = width; - } - } - } - minColumnWidths[colIndex] = colW; - } - needsLayout = true; - // ensure colspanned columns have enough space - columnWidths = cloneArray(minColumnWidths); - distributeColSpanWidths(); - reDistributeColSpanWidths = false; - } - - if (reDistributeColSpanWidths) { - distributeColSpanWidths(); - } - - if (dirtyRows.size() > 0) { - needsLayout = true; - for (Integer rowIndex : dirtyRows) { - // recalculate min row height - int rowH = minRowHeights[rowIndex] = 0; - // loop all columns on row rowIndex - for (int i = 0; i < columnWidths.length; i++) { - Cell cell = cells[i][rowIndex]; - if (cell != null && cell.getChildUIDL() != null - && !cell.hasRelativeHeight() && cell.rowspan == 1) { - int h = cell.getHeight(); - if (h > rowH) { - rowH = h; - } - } - } - minRowHeights[rowIndex] = rowH; - } - // TODO could check only some row spans - rowHeights = cloneArray(minRowHeights); - distributeRowSpanHeights(); - reDistributeRowSpanHeights = false; - } - - if (reDistributeRowSpanHeights) { - distributeRowSpanHeights(); - } - - if (needsLayout) { - expandColumns(); - expandRows(); - layoutCells(); - // loop all relative sized components and update their size - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null - && cell.cc != null - && (cell.hasRelativeHeight() || cell - .hasRelativeWidth())) { - client.handleComponentRelativeSize(cell.cc.getWidget()); - } - } - } - } - if (canvas.getOffsetHeight() != offsetHeight - || canvas.getOffsetWidth() != offsetWidth) { - return false; - } else { - return true; - } - } - - public RenderSpace getAllocatedSpace(Widget child) { - Cell cell = paintableToCell.get(child); - assert cell != null; - return cell.getAllocatedSpace(); - } - - private Cell[][] cells; - - /** - * Private helper class. - */ - private class Cell { - private boolean relHeight = false; - private boolean relWidth = false; - private boolean widthCanAffectHeight = false; - - public Cell(UIDL c) { - row = c.getIntAttribute("y"); - col = c.getIntAttribute("x"); - setUidl(c); - } - - public boolean widthCanAffectHeight() { - return widthCanAffectHeight; - } - - public boolean hasRelativeHeight() { - return relHeight; - } - - public RenderSpace getAllocatedSpace() { - return new RenderSpace(getAvailableWidth() - - cc.getCaptionWidthAfterComponent(), getAvailableHeight() - - cc.getCaptionHeightAboveComponent()); - } - - public boolean hasContent() { - return childUidl != null; - } - - /** - * @return total of spanned cols - */ - private int getAvailableWidth() { - int width = columnWidths[col]; - for (int i = 1; i < colspan; i++) { - width += spacingPixelsHorizontal + columnWidths[col + i]; - } - return width; - } - - /** - * @return total of spanned rows - */ - private int getAvailableHeight() { - int height = rowHeights[row]; - for (int i = 1; i < rowspan; i++) { - height += spacingPixelsVertical + rowHeights[row + i]; - } - return height; - } - - public void layout(int x, int y) { - if (cc != null && cc.isAttached()) { - canvas.setWidgetPosition(cc, x, y); - cc.setContainerSize(getAvailableWidth(), getAvailableHeight()); - cc.setAlignment(new AlignmentInfo(alignment)); - cc.updateAlignments(getAvailableWidth(), getAvailableHeight()); - } - } - - public int getWidth() { - if (cc != null) { - int w = cc.getWidgetSize().getWidth() - + cc.getCaptionWidthAfterComponent(); - return w; - } else { - return 0; - } - } - - public int getHeight() { - if (cc != null) { - return cc.getWidgetSize().getHeight() - + cc.getCaptionHeightAboveComponent(); - } else { - return 0; - } - } - - public boolean renderIfNoRelativeWidth() { - if (childUidl == null) { - return false; - } - if (!hasRelativeWidth()) { - render(); - return true; - } else { - return false; - } - } - - protected boolean hasRelativeWidth() { - return relWidth; - } - - protected void render() { - assert childUidl != null; - - Paintable paintable = client.getPaintable(childUidl); - assert paintable != null; - if (cc == null || cc.getWidget() != paintable) { - if (widgetToComponentContainer.containsKey(paintable)) { - // Component moving from one place to another - cc = widgetToComponentContainer.get(paintable); - cc.setWidth(""); - cc.setHeight(""); - /* - * Widget might not be set if moving from another component - * and this layout has been hidden when moving out, see - * #5372 - */ - cc.setWidget((Widget) paintable); - } else { - // A new component - cc = new ChildComponentContainer((Widget) paintable, - CellBasedLayout.ORIENTATION_VERTICAL); - widgetToComponentContainer.put((Widget) paintable, cc); - cc.setWidth(""); - canvas.add(cc, 0, 0); - } - paintableToCell.put(paintable, this); - } - cc.renderChild(childUidl, client, -1); - if (sizeChangedDuringRendering && Util.isCached(childUidl)) { - client.handleComponentRelativeSize(cc.getWidget()); - } - cc.updateWidgetSize(); - nonRenderedWidgets.remove(paintable); - } - - public UIDL getChildUIDL() { - return childUidl; - } - - final int row; - final int col; - int colspan = 1; - int rowspan = 1; - UIDL childUidl; - int alignment; - // may be null after setUidl() if content has vanished or changed, set - // in render() - ChildComponentContainer cc; - - public void setUidl(UIDL c) { - // Set cell width - colspan = c.hasAttribute("w") ? c.getIntAttribute("w") : 1; - // Set cell height - rowspan = c.hasAttribute("h") ? c.getIntAttribute("h") : 1; - // ensure we will lose reference to old cells, now overlapped by - // this cell - for (int i = 0; i < colspan; i++) { - for (int j = 0; j < rowspan; j++) { - if (i > 0 || j > 0) { - cells[col + i][row + j] = null; - } - } - } - - c = c.getChildUIDL(0); // we are interested about childUidl - if (childUidl != null) { - if (c == null) { - // content has vanished, old content will be removed from - // canvas later during the render phase - cc = null; - } else if (cc != null - && cc.getWidget() != client.getPaintable(c)) { - // content has changed - cc = null; - Paintable paintable = client.getPaintable(c); - if (widgetToComponentContainer.containsKey(paintable)) { - // cc exist for this component (moved) use that for this - // cell - cc = widgetToComponentContainer.get(paintable); - cc.setWidth(""); - cc.setHeight(""); - paintableToCell.put(paintable, this); - } - } - } - childUidl = c; - updateRelSizeStatus(c); - } - - protected void updateRelSizeStatus(UIDL uidl) { - if (uidl != null && !uidl.getBooleanAttribute("cached")) { - if (uidl.hasAttribute("height") - && uidl.getStringAttribute("height").contains("%")) { - relHeight = true; - } else { - relHeight = false; - } - if (uidl.hasAttribute("width")) { - widthCanAffectHeight = relWidth = uidl.getStringAttribute( - "width").contains("%"); - if (uidl.hasAttribute("height")) { - widthCanAffectHeight = false; - } - } else { - widthCanAffectHeight = !uidl.hasAttribute("height"); - relWidth = false; - } - } - } - } - - private Cell getCell(UIDL c) { - int row = c.getIntAttribute("y"); - int col = c.getIntAttribute("x"); - Cell cell = cells[col][row]; - if (cell == null) { - cell = new Cell(c); - cells[col][row] = cell; - } else { - cell.setUidl(c); - } - return cell; - } - - /** - * Returns the deepest nested child component which contains "element". The - * child component is also returned if "element" is part of its caption. - * - * @param element - * An element that is a nested sub element of the root element in - * this layout - * @return The Paintable which the element is a part of. Null if the element - * belongs to the layout and not to a child. - */ - private Paintable getComponent(Element element) { - return Util.getPaintableForElement(client, this, element); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayout.java deleted file mode 100644 index b3a036f748..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayout.java +++ /dev/null @@ -1,14 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -public class VHorizontalLayout extends VOrderedLayout { - - public static final String CLASSNAME = "v-horizontallayout"; - - public VHorizontalLayout() { - super(CLASSNAME, ORIENTATION_HORIZONTAL); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VLabel.java b/src/com/vaadin/terminal/gwt/client/ui/VLabel.java deleted file mode 100644 index 341e9f3484..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VLabel.java +++ /dev/null @@ -1,135 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.PreElement; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VLabel extends HTML implements Paintable { - - public static final String CLASSNAME = "v-label"; - private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w"; - - private ApplicationConnection client; - private int verticalPaddingBorder = 0; - private int horizontalPaddingBorder = 0; - - public VLabel() { - super(); - setStyleName(CLASSNAME); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - public VLabel(String text) { - super(text); - setStyleName(CLASSNAME); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - event.cancelBubble(true); - return; - } - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - if (client.updateComponent(this, uidl, true)) { - return; - } - - this.client = client; - - boolean sinkOnloads = false; - - final String mode = uidl.getStringAttribute("mode"); - if (mode == null || "text".equals(mode)) { - setText(uidl.getChildString(0)); - } else if ("pre".equals(mode)) { - PreElement preElement = Document.get().createPreElement(); - preElement.setInnerText(uidl.getChildUIDL(0).getChildString(0)); - // clear existing content - setHTML(""); - // add preformatted text to dom - getElement().appendChild(preElement); - } else if ("uidl".equals(mode)) { - setHTML(uidl.getChildrenAsXML()); - } else if ("xhtml".equals(mode)) { - UIDL content = uidl.getChildUIDL(0).getChildUIDL(0); - if (content.getChildCount() > 0) { - setHTML(content.getChildString(0)); - } else { - setHTML(""); - } - sinkOnloads = true; - } else if ("xml".equals(mode)) { - setHTML(uidl.getChildUIDL(0).getChildString(0)); - } else if ("raw".equals(mode)) { - setHTML(uidl.getChildUIDL(0).getChildString(0)); - sinkOnloads = true; - } else { - setText(""); - } - if (sinkOnloads) { - sinkOnloadsForContainedImgs(); - } - } - - private void sinkOnloadsForContainedImgs() { - NodeList<Element> images = getElement().getElementsByTagName("img"); - for (int i = 0; i < images.getLength(); i++) { - Element img = images.getItem(i); - DOM.sinkEvents((com.google.gwt.user.client.Element) img, - Event.ONLOAD); - } - - } - - @Override - public void setHeight(String height) { - verticalPaddingBorder = Util.setHeightExcludingPaddingAndBorder(this, - height, verticalPaddingBorder); - } - - @Override - public void setWidth(String width) { - horizontalPaddingBorder = Util.setWidthExcludingPaddingAndBorder(this, - width, horizontalPaddingBorder); - if (width == null || width.equals("")) { - setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true); - } else { - setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false); - } - } - - @Override - public void setText(String text) { - if (BrowserInfo.get().isIE() && BrowserInfo.get().getIEVersion() < 9) { - // #3983 - IE6-IE8 incorrectly replaces \n with <br> so we do the - // escaping manually and set as HTML - super.setHTML(Util.escapeHTML(text)); - } else { - super.setText(text); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VLink.java b/src/com/vaadin/terminal/gwt/client/ui/VLink.java deleted file mode 100644 index b8030de421..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VLink.java +++ /dev/null @@ -1,182 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VLink extends HTML implements Paintable, ClickHandler { - - public static final String CLASSNAME = "v-link"; - - private static final int BORDER_STYLE_DEFAULT = 0; - private static final int BORDER_STYLE_MINIMAL = 1; - private static final int BORDER_STYLE_NONE = 2; - - private String src; - - private String target; - - private int borderStyle = BORDER_STYLE_DEFAULT; - - private boolean enabled; - - private boolean readonly; - - private int targetWidth; - - private int targetHeight; - - private Element errorIndicatorElement; - - private final Element anchor = DOM.createAnchor(); - - private final Element captionElement = DOM.createSpan(); - - private Icon icon; - - private ApplicationConnection client; - - public VLink() { - super(); - getElement().appendChild(anchor); - anchor.appendChild(captionElement); - addClickHandler(this); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - setStyleName(CLASSNAME); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - // Ensure correct implementation, - // but don't let container manage caption etc. - if (client.updateComponent(this, uidl, false)) { - return; - } - - this.client = client; - - enabled = uidl.hasAttribute("disabled") ? false : true; - readonly = uidl.hasAttribute("readonly") ? true : false; - - if (uidl.hasAttribute("name")) { - target = uidl.getStringAttribute("name"); - anchor.setAttribute("target", target); - } - if (uidl.hasAttribute("src")) { - src = client.translateVaadinUri(uidl.getStringAttribute("src")); - anchor.setAttribute("href", src); - } - - if (uidl.hasAttribute("border")) { - if ("none".equals(uidl.getStringAttribute("border"))) { - borderStyle = BORDER_STYLE_NONE; - } else { - borderStyle = BORDER_STYLE_MINIMAL; - } - } else { - borderStyle = BORDER_STYLE_DEFAULT; - } - - targetHeight = uidl.hasAttribute("targetHeight") ? uidl - .getIntAttribute("targetHeight") : -1; - targetWidth = uidl.hasAttribute("targetWidth") ? uidl - .getIntAttribute("targetWidth") : -1; - - // Set link caption - captionElement.setInnerText(uidl.getStringAttribute("caption")); - - // handle error - if (uidl.hasAttribute("error")) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createDiv(); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - } - DOM.insertChild(getElement(), errorIndicatorElement, 0); - } else if (errorIndicatorElement != null) { - DOM.setStyleAttribute(errorIndicatorElement, "display", "none"); - } - - if (uidl.hasAttribute("icon")) { - if (icon == null) { - icon = new Icon(client); - anchor.insertBefore(icon.getElement(), captionElement); - } - icon.setUri(uidl.getStringAttribute("icon")); - } - - } - - public void onClick(ClickEvent event) { - if (enabled && !readonly) { - if (target == null) { - target = "_self"; - } - String features; - switch (borderStyle) { - case BORDER_STYLE_NONE: - features = "menubar=no,location=no,status=no"; - break; - case BORDER_STYLE_MINIMAL: - features = "menubar=yes,location=no,status=no"; - break; - default: - features = ""; - break; - } - - if (targetWidth > 0) { - features += (features.length() > 0 ? "," : "") + "width=" - + targetWidth; - } - if (targetHeight > 0) { - features += (features.length() > 0 ? "," : "") + "height=" - + targetHeight; - } - - if (features.length() > 0) { - // if 'special features' are set, use window.open(), unless - // a modifier key is held (ctrl to open in new tab etc) - Event e = DOM.eventGetCurrentEvent(); - if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey() - && !e.getMetaKey()) { - Window.open(src, target, features); - e.preventDefault(); - } - } - } - } - - @Override - public void onBrowserEvent(Event event) { - final Element target = DOM.eventGetTarget(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - } - if (client != null) { - client.handleTooltipEvent(event, this); - } - if (target == captionElement || target == anchor - || (icon != null && target == icon.getElement())) { - super.onBrowserEvent(event); - } - if (!enabled) { - event.preventDefault(); - } - - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VMediaBase.java b/src/com/vaadin/terminal/gwt/client/ui/VMediaBase.java index 53638917b2..6c5fbc2ef0 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VMediaBase.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VMediaBase.java @@ -8,25 +8,10 @@ import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.MediaElement; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -public abstract class VMediaBase extends Widget implements Paintable { - public static final String ATTR_PAUSE = "pause"; - public static final String ATTR_PLAY = "play"; - public static final String ATTR_MUTED = "muted"; - public static final String ATTR_CONTROLS = "ctrl"; - public static final String ATTR_AUTOPLAY = "auto"; - public static final String TAG_SOURCE = "src"; - public static final String ATTR_RESOURCE = "res"; - public static final String ATTR_RESOURCE_TYPE = "type"; - public static final String ATTR_HTML = "html"; - public static final String ATTR_ALT_TEXT = "alt"; +public abstract class VMediaBase extends Widget { private MediaElement media; - protected ApplicationConnection client; /** * Sets the MediaElement that is to receive all commands and properties. @@ -38,96 +23,40 @@ public abstract class VMediaBase extends Widget implements Paintable { media = element; } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, true)) { - return; - } - - this.client = client; - - media.setControls(shouldShowControls(uidl)); - media.setAutoplay(shouldAutoplay(uidl)); - media.setMuted(isMediaMuted(uidl)); - - // Add all sources - for (int ix = 0; ix < uidl.getChildCount(); ix++) { - UIDL child = uidl.getChildUIDL(ix); - if (TAG_SOURCE.equals(child.getTag())) { - Element src = Document.get().createElement("source").cast(); - src.setAttribute("src", getSourceUrl(child)); - src.setAttribute("type", getSourceType(child)); - media.appendChild(src); - } - } - setAltText(uidl); - - evalPauseCommand(uidl); - evalPlayCommand(uidl); - } - - protected boolean shouldShowControls(UIDL uidl) { - return uidl.getBooleanAttribute(ATTR_CONTROLS); - } - - private boolean shouldAutoplay(UIDL uidl) { - return uidl.getBooleanAttribute(ATTR_AUTOPLAY); - } - - private boolean isMediaMuted(UIDL uidl) { - return uidl.getBooleanAttribute(ATTR_MUTED); - } - /** - * @param uidl - * @return the URL of a resource to be used as a source for the media + * @return the default HTML to show users with browsers that do not support + * HTML5 media markup. */ - private String getSourceUrl(UIDL uidl) { - String url = client.translateVaadinUri(uidl - .getStringAttribute(ATTR_RESOURCE)); - if (url == null) { - return ""; - } - return url; - } + protected abstract String getDefaultAltHtml(); - /** - * @param uidl - * @return the mime type of the media - */ - private String getSourceType(UIDL uidl) { - return uidl.getStringAttribute(ATTR_RESOURCE_TYPE); + public void play() { + media.play(); } - private void setAltText(UIDL uidl) { - String alt = uidl.getStringAttribute(ATTR_ALT_TEXT); + public void pause() { + media.pause(); + } - if (alt == null || "".equals(alt)) { - alt = getDefaultAltHtml(); - } else if (!allowHtmlContent(uidl)) { - alt = Util.escapeHTML(alt); - } + public void setAltText(String alt) { media.appendChild(Document.get().createTextNode(alt)); } - private boolean allowHtmlContent(UIDL uidl) { - return uidl.getBooleanAttribute(ATTR_HTML); + public void setControls(boolean shouldShowControls) { + media.setControls(shouldShowControls); } - private void evalPlayCommand(UIDL uidl) { - if (uidl.hasAttribute(ATTR_PLAY)) { - media.play(); - } + public void setAutoplay(boolean shouldAutoplay) { + media.setAutoplay(shouldAutoplay); } - private void evalPauseCommand(UIDL uidl) { - if (uidl.hasAttribute(ATTR_PAUSE)) { - media.pause(); - } + public void setMuted(boolean mediaMuted) { + media.setMuted(mediaMuted); } - /** - * @return the default HTML to show users with browsers that do not support - * HTML5 media markup. - */ - protected abstract String getDefaultAltHtml(); + public void addSource(String sourceUrl, String sourceType) { + Element src = Document.get().createElement("source").cast(); + src.setAttribute("src", sourceUrl); + src.setAttribute("type", sourceType); + media.appendChild(src); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java deleted file mode 100644 index 98d3b505ae..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java +++ /dev/null @@ -1,238 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Button; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.EventHelper; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VNativeButton extends Button implements Paintable, ClickHandler, - FocusHandler, BlurHandler { - - public static final String CLASSNAME = "v-nativebutton"; - - protected String width = null; - - protected String id; - - protected ApplicationConnection client; - - protected Element errorIndicatorElement; - - protected final Element captionElement = DOM.createSpan(); - - protected Icon icon; - - /** - * Helper flag to handle special-case where the button is moved from under - * mouse while clicking it. In this case mouse leaves the button without - * moving. - */ - private boolean clickPending; - - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; - - private boolean disableOnClick = false; - - public VNativeButton() { - setStyleName(CLASSNAME); - - getElement().appendChild(captionElement); - captionElement.setClassName(getStyleName() + "-caption"); - - addClickHandler(this); - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - sinkEvents(Event.ONMOUSEDOWN); - sinkEvents(Event.ONMOUSEUP); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - // Ensure correct implementation, - // but don't let container manage caption etc. - if (client.updateComponent(this, uidl, false)) { - return; - } - - disableOnClick = uidl.hasAttribute(VButton.ATTR_DISABLE_ON_CLICK); - - focusHandlerRegistration = EventHelper.updateFocusHandler(this, client, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, client, - blurHandlerRegistration); - - // Save details - this.client = client; - id = uidl.getId(); - - // Set text - setText(uidl.getStringAttribute("caption")); - - // handle error - if (uidl.hasAttribute("error")) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createSpan(); - errorIndicatorElement.setClassName("v-errorindicator"); - } - getElement().insertBefore(errorIndicatorElement, captionElement); - - // Fix for IE6, IE7 - if (BrowserInfo.get().isIE()) { - errorIndicatorElement.setInnerText(" "); - } - - } else if (errorIndicatorElement != null) { - getElement().removeChild(errorIndicatorElement); - errorIndicatorElement = null; - } - - if (uidl.hasAttribute("icon")) { - if (icon == null) { - icon = new Icon(client); - getElement().insertBefore(icon.getElement(), captionElement); - } - icon.setUri(uidl.getStringAttribute("icon")); - } else { - if (icon != null) { - getElement().removeChild(icon.getElement()); - icon = null; - } - } - - if (BrowserInfo.get().isIE7()) { - /* - * Workaround for IE7 size calculation issues. Deferred because of - * issues with a button with an icon using the reindeer theme - */ - if (width.equals("")) { - Scheduler.get().scheduleDeferred(new Command() { - - public void execute() { - setWidth(""); - setWidth(getOffsetWidth() + "px"); - } - }); - } - } - } - - @Override - public void setText(String text) { - captionElement.setInnerText(text); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - if (DOM.eventGetType(event) == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - - } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN - && event.getButton() == Event.BUTTON_LEFT) { - clickPending = true; - } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) { - clickPending = false; - } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) { - if (clickPending) { - click(); - } - clickPending = false; - } - - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - - @Override - public void setWidth(String width) { - /* Workaround for IE7 button size part 1 (#2014) */ - if (BrowserInfo.get().isIE7() && this.width != null) { - if (this.width.equals(width)) { - return; - } - - if (width == null) { - width = ""; - } - } - - this.width = width; - super.setWidth(width); - - /* Workaround for IE7 button size part 2 (#2014) */ - if (BrowserInfo.get().isIE7()) { - super.setWidth(width); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event - * .dom.client.ClickEvent) - */ - public void onClick(ClickEvent event) { - if (id == null || client == null) { - return; - } - - if (BrowserInfo.get().isSafari()) { - VNativeButton.this.setFocus(true); - } - if (disableOnClick) { - setEnabled(false); - client.updateVariable(id, "disabledOnClick", true, false); - } - - // Add mouse details - MouseEventDetails details = new MouseEventDetails( - event.getNativeEvent(), getElement()); - client.updateVariable(id, "mousedetails", details.serialize(), false); - - client.updateVariable(id, "state", true, true); - clickPending = false; - } - - public void onFocus(FocusEvent arg0) { - client.updateVariable(id, EventId.FOCUS, "", true); - } - - public void onBlur(BlurEvent arg0) { - client.updateVariable(id, EventId.BLUR, "", true); - } - - @Override - public void setEnabled(boolean enabled) { - if (isEnabled() != enabled) { - super.setEnabled(enabled); - setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled); - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayout.java deleted file mode 100644 index ecdb171ec4..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayout.java +++ /dev/null @@ -1,969 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; -import com.vaadin.terminal.gwt.client.RenderInformation.Size; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ValueMap; -import com.vaadin.terminal.gwt.client.ui.layout.CellBasedLayout; -import com.vaadin.terminal.gwt.client.ui.layout.ChildComponentContainer; - -public class VOrderedLayout extends CellBasedLayout { - - public static final String CLASSNAME = "v-orderedlayout"; - - private int orientation; - - // Can be removed once OrderedLayout is removed - private boolean allowOrientationUpdate = false; - - /** - * Size of the layout excluding any margins. - */ - private Size activeLayoutSize = new Size(0, 0); - - private boolean isRendering = false; - - private String width = ""; - - private boolean sizeHasChangedDuringRendering = false; - - private ValueMap expandRatios; - - private double expandRatioSum; - - private double defaultExpandRatio; - - private ValueMap alignments; - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this, EventId.LAYOUT_CLICK) { - - @Override - protected Paintable getChildComponent(Element element) { - return getComponent(element); - } - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - }; - - public VOrderedLayout() { - this(CLASSNAME, ORIENTATION_VERTICAL); - allowOrientationUpdate = true; - } - - protected VOrderedLayout(String className, int orientation) { - setStyleName(className); - this.orientation = orientation; - - STYLENAME_SPACING = className + "-spacing"; - STYLENAME_MARGIN_TOP = className + "-margin-top"; - STYLENAME_MARGIN_RIGHT = className + "-margin-right"; - STYLENAME_MARGIN_BOTTOM = className + "-margin-bottom"; - STYLENAME_MARGIN_LEFT = className + "-margin-left"; - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - isRendering = true; - super.updateFromUIDL(uidl, client); - - // Only non-cached, visible UIDL:s can introduce changes - if (uidl.getBooleanAttribute("cached") - || uidl.getBooleanAttribute("invisible")) { - isRendering = false; - return; - } - - clickEventHandler.handleEventHandlerRegistration(client); - - if (allowOrientationUpdate) { - handleOrientationUpdate(uidl); - } - - // IStopWatch w = new IStopWatch("OrderedLayout.updateFromUIDL"); - - ArrayList<Widget> uidlWidgets = new ArrayList<Widget>( - uidl.getChildCount()); - ArrayList<ChildComponentContainer> relativeSizeComponents = new ArrayList<ChildComponentContainer>(); - ArrayList<UIDL> relativeSizeComponentUIDL = new ArrayList<UIDL>(); - - int pos = 0; - for (final Iterator<Object> it = uidl.getChildIterator(); it.hasNext();) { - final UIDL childUIDL = (UIDL) it.next(); - final Paintable child = client.getPaintable(childUIDL); - Widget widget = (Widget) child; - - // Create container for component - ChildComponentContainer childComponentContainer = getComponentContainer(widget); - - if (childComponentContainer == null) { - // This is a new component - childComponentContainer = createChildContainer(widget); - } else { - /* - * The widget may be null if the same paintable has been - * rendered in a different component container while this has - * been invisible. Ensure the childComponentContainer has the - * widget attached. See e.g. #5372 - */ - childComponentContainer.setWidget(widget); - } - - addOrMoveChild(childComponentContainer, pos++); - - /* - * Components which are to be expanded in the same orientation as - * the layout are rendered later when it is clear how much space - * they can use - */ - if (!Util.isCached(childUIDL)) { - FloatSize relativeSize = Util.parseRelativeSize(childUIDL); - childComponentContainer.setRelativeSize(relativeSize); - } - - if (childComponentContainer.isComponentRelativeSized(orientation)) { - relativeSizeComponents.add(childComponentContainer); - relativeSizeComponentUIDL.add(childUIDL); - } else { - if (isDynamicWidth()) { - childComponentContainer.renderChild(childUIDL, client, -1); - } else { - childComponentContainer.renderChild(childUIDL, client, - activeLayoutSize.getWidth()); - } - if (sizeHasChangedDuringRendering && Util.isCached(childUIDL)) { - // notify cached relative sized component about size - // chance - client.handleComponentRelativeSize(childComponentContainer - .getWidget()); - } - } - - uidlWidgets.add(widget); - - } - - // w.mark("Rendering of " - // + (uidlWidgets.size() - relativeSizeComponents.size()) - // + " absolute size components done"); - - /* - * Remove any children after pos. These are the ones that previously - * were in the layout but have now been removed - */ - removeChildrenAfter(pos); - - // w.mark("Old children removed"); - - /* Fetch alignments and expand ratio from UIDL */ - updateAlignmentsAndExpandRatios(uidl, uidlWidgets); - // w.mark("Alignments and expand ratios updated"); - - /* Fetch widget sizes from rendered components */ - updateWidgetSizes(); - // w.mark("Widget sizes updated"); - - recalculateLayout(); - // w.mark("Layout size calculated (" + activeLayoutSize + - // ") offsetSize: " - // + getOffsetWidth() + "," + getOffsetHeight()); - - /* Render relative size components */ - for (int i = 0; i < relativeSizeComponents.size(); i++) { - ChildComponentContainer childComponentContainer = relativeSizeComponents - .get(i); - UIDL childUIDL = relativeSizeComponentUIDL.get(i); - - if (isDynamicWidth()) { - childComponentContainer.renderChild(childUIDL, client, -1); - } else { - childComponentContainer.renderChild(childUIDL, client, - activeLayoutSize.getWidth()); - } - - if (Util.isCached(childUIDL)) { - /* - * We must update the size of the relative sized component if - * the expand ratio or something else in the layout changes - * which affects the size of a relative sized component - */ - client.handleComponentRelativeSize(childComponentContainer - .getWidget()); - } - - // childComponentContainer.updateWidgetSize(); - } - - // w.mark("Rendering of " + (relativeSizeComponents.size()) - // + " relative size components done"); - - /* Fetch widget sizes for relative size components */ - for (ChildComponentContainer childComponentContainer : widgetToComponentContainer - .values()) { - - /* Update widget size from DOM */ - childComponentContainer.updateWidgetSize(); - } - - // w.mark("Widget sizes updated"); - - /* - * Components with relative size in main direction may affect the layout - * size in the other direction - */ - if ((isHorizontal() && isDynamicHeight()) - || (isVertical() && isDynamicWidth())) { - layoutSizeMightHaveChanged(); - } - // w.mark("Layout dimensions updated"); - - /* Update component spacing */ - updateContainerMargins(); - - /* - * Update component sizes for components with relative size in non-main - * direction - */ - if (updateRelativeSizesInNonMainDirection()) { - // Sizes updated - might affect the other dimension so we need to - // recheck the widget sizes and recalculate layout dimensions - updateWidgetSizes(); - layoutSizeMightHaveChanged(); - } - calculateAlignments(); - // w.mark("recalculateComponentSizesAndAlignments done"); - - setRootSize(); - - if (BrowserInfo.get().isIE()) { - /* - * This should fix the issue with padding not always taken into - * account for the containers leading to no spacing between - * elements. - */ - root.getStyle().setProperty("zoom", "1"); - } - - // w.mark("runDescendentsLayout done"); - isRendering = false; - sizeHasChangedDuringRendering = false; - } - - private void layoutSizeMightHaveChanged() { - Size oldSize = new Size(activeLayoutSize.getWidth(), - activeLayoutSize.getHeight()); - calculateLayoutDimensions(); - - /* - * If layout dimension changes we must also update container sizes - */ - if (!oldSize.equals(activeLayoutSize)) { - calculateContainerSize(); - } - } - - private void updateWidgetSizes() { - for (ChildComponentContainer childComponentContainer : widgetToComponentContainer - .values()) { - - /* - * Update widget size from DOM - */ - childComponentContainer.updateWidgetSize(); - } - } - - private void recalculateLayout() { - - /* Calculate space for relative size components */ - int spaceForExpansion = calculateLayoutDimensions(); - - if (!widgetToComponentContainer.isEmpty()) { - /* Divide expansion space between component containers */ - expandComponentContainers(spaceForExpansion); - - /* Update container sizes */ - calculateContainerSize(); - } - - } - - private void expandComponentContainers(int spaceForExpansion) { - int remaining = spaceForExpansion; - for (ChildComponentContainer childComponentContainer : widgetToComponentContainer - .values()) { - remaining -= childComponentContainer.expand(orientation, - spaceForExpansion); - } - - if (remaining > 0) { - - // Some left-over pixels due to rounding errors - - // Add one pixel to each container until there are no pixels left - // FIXME extra pixels should be divided among expanded widgets if - // such a widgets exists - - Iterator<Widget> widgetIterator = iterator(); - while (widgetIterator.hasNext() && remaining-- > 0) { - ChildComponentContainer childComponentContainer = (ChildComponentContainer) widgetIterator - .next(); - childComponentContainer.expandExtra(orientation, 1); - } - } - - } - - private void handleOrientationUpdate(UIDL uidl) { - int newOrientation = ORIENTATION_VERTICAL; - if ("horizontal".equals(uidl.getStringAttribute("orientation"))) { - newOrientation = ORIENTATION_HORIZONTAL; - } - - if (orientation != newOrientation) { - orientation = newOrientation; - - for (ChildComponentContainer childComponentContainer : widgetToComponentContainer - .values()) { - childComponentContainer.setOrientation(orientation); - } - } - - } - - /** - * Updated components with relative height in horizontal layouts and - * components with relative width in vertical layouts. This is only needed - * if the height (horizontal layout) or width (vertical layout) has not been - * specified. - */ - private boolean updateRelativeSizesInNonMainDirection() { - int updateDirection = 1 - orientation; - if ((updateDirection == ORIENTATION_HORIZONTAL && !isDynamicWidth()) - || (updateDirection == ORIENTATION_VERTICAL && !isDynamicHeight())) { - return false; - } - - boolean updated = false; - for (ChildComponentContainer componentContainer : widgetToComponentContainer - .values()) { - if (componentContainer.isComponentRelativeSized(updateDirection)) { - client.handleComponentRelativeSize(componentContainer - .getWidget()); - } - - updated = true; - } - - return updated; - } - - private int calculateLayoutDimensions() { - int summedWidgetWidth = 0; - int summedWidgetHeight = 0; - - int maxWidgetWidth = 0; - int maxWidgetHeight = 0; - - // Calculate layout dimensions from component dimensions - for (ChildComponentContainer childComponentContainer : widgetToComponentContainer - .values()) { - - int widgetHeight = 0; - int widgetWidth = 0; - if (childComponentContainer.isComponentRelativeSized(orientation)) { - if (orientation == ORIENTATION_HORIZONTAL) { - widgetHeight = getWidgetHeight(childComponentContainer); - } else { - widgetWidth = getWidgetWidth(childComponentContainer); - } - } else { - widgetWidth = getWidgetWidth(childComponentContainer); - widgetHeight = getWidgetHeight(childComponentContainer); - } - - summedWidgetWidth += widgetWidth; - summedWidgetHeight += widgetHeight; - - maxWidgetHeight = Math.max(maxWidgetHeight, widgetHeight); - maxWidgetWidth = Math.max(maxWidgetWidth, widgetWidth); - } - - if (isHorizontal()) { - summedWidgetWidth += activeSpacing.hSpacing - * (widgetToComponentContainer.size() - 1); - } else { - summedWidgetHeight += activeSpacing.vSpacing - * (widgetToComponentContainer.size() - 1); - } - - Size layoutSize = updateLayoutDimensions(summedWidgetWidth, - summedWidgetHeight, maxWidgetWidth, maxWidgetHeight); - - int remainingSpace; - if (isHorizontal()) { - remainingSpace = layoutSize.getWidth() - summedWidgetWidth; - } else { - remainingSpace = layoutSize.getHeight() - summedWidgetHeight; - } - if (remainingSpace < 0) { - remainingSpace = 0; - } - - // ApplicationConnection.getConsole().log( - // "Layout size: " + activeLayoutSize); - return remainingSpace; - } - - private int getWidgetHeight(ChildComponentContainer childComponentContainer) { - Size s = childComponentContainer.getWidgetSize(); - return s.getHeight() - + childComponentContainer.getCaptionHeightAboveComponent(); - } - - private int getWidgetWidth(ChildComponentContainer childComponentContainer) { - Size s = childComponentContainer.getWidgetSize(); - int widgetWidth = s.getWidth() - + childComponentContainer.getCaptionWidthAfterComponent(); - - /* - * If the component does not have a specified size in the main direction - * the caption may determine the space used by the component - */ - if (!childComponentContainer.widgetHasSizeSpecified(orientation)) { - int captionWidth = childComponentContainer - .getCaptionRequiredWidth(); - - if (captionWidth > widgetWidth) { - widgetWidth = captionWidth; - } - } - - return widgetWidth; - } - - private void calculateAlignments() { - int w = 0; - int h = 0; - - if (isHorizontal()) { - // HORIZONTAL - h = activeLayoutSize.getHeight(); - if (!isDynamicWidth()) { - w = -1; - } - - } else { - // VERTICAL - w = activeLayoutSize.getWidth(); - if (!isDynamicHeight()) { - h = -1; - } - } - - for (ChildComponentContainer childComponentContainer : widgetToComponentContainer - .values()) { - childComponentContainer.updateAlignments(w, h); - } - - } - - private void calculateContainerSize() { - - /* - * Container size here means the size the container gets from the - * component. The expansion size is not include in this but taken - * separately into account. - */ - int height = 0, width = 0; - Iterator<Widget> widgetIterator = iterator(); - if (isHorizontal()) { - height = activeLayoutSize.getHeight(); - int availableWidth = activeLayoutSize.getWidth(); - boolean first = true; - while (widgetIterator.hasNext()) { - ChildComponentContainer childComponentContainer = (ChildComponentContainer) widgetIterator - .next(); - if (!childComponentContainer - .isComponentRelativeSized(ORIENTATION_HORIZONTAL)) { - /* - * Only components with non-relative size in the main - * direction has a container size - */ - width = childComponentContainer.getWidgetSize().getWidth() - + childComponentContainer - .getCaptionWidthAfterComponent(); - - /* - * If the component does not have a specified size in the - * main direction the caption may determine the space used - * by the component - */ - if (!childComponentContainer - .widgetHasSizeSpecified(orientation)) { - int captionWidth = childComponentContainer - .getCaptionRequiredWidth(); - // ApplicationConnection.getConsole().log( - // "Component width: " + width - // + ", caption width: " + captionWidth); - if (captionWidth > width) { - width = captionWidth; - } - } - } else { - width = 0; - } - - if (!isDynamicWidth()) { - if (availableWidth == 0) { - /* - * Let the overflowing components overflow. IE has - * problems with zero sizes. - */ - // width = 0; - // height = 0; - } else if (width > availableWidth) { - width = availableWidth; - - if (!first) { - width -= activeSpacing.hSpacing; - } - availableWidth = 0; - } else { - availableWidth -= width; - if (!first) { - availableWidth -= activeSpacing.hSpacing; - } - } - - first = false; - } - - childComponentContainer.setContainerSize(width, height); - } - } else { - width = activeLayoutSize.getWidth(); - while (widgetIterator.hasNext()) { - ChildComponentContainer childComponentContainer = (ChildComponentContainer) widgetIterator - .next(); - - if (!childComponentContainer - .isComponentRelativeSized(ORIENTATION_VERTICAL)) { - /* - * Only components with non-relative size in the main - * direction has a container size - */ - height = childComponentContainer.getWidgetSize() - .getHeight() - + childComponentContainer - .getCaptionHeightAboveComponent(); - } else { - height = 0; - } - - childComponentContainer.setContainerSize(width, height); - } - - } - - } - - private Size updateLayoutDimensions(int totalComponentWidth, - int totalComponentHeight, int maxComponentWidth, - int maxComponentHeight) { - - /* Only need to calculate dynamic dimensions */ - if (!isDynamicHeight() && !isDynamicWidth()) { - return activeLayoutSize; - } - - int activeLayoutWidth = 0; - int activeLayoutHeight = 0; - - // Update layout dimensions - if (isHorizontal()) { - // Horizontal - if (isDynamicWidth()) { - activeLayoutWidth = totalComponentWidth; - } - - if (isDynamicHeight()) { - activeLayoutHeight = maxComponentHeight; - } - - } else { - // Vertical - if (isDynamicWidth()) { - activeLayoutWidth = maxComponentWidth; - } - - if (isDynamicHeight()) { - activeLayoutHeight = totalComponentHeight; - } - } - - if (isDynamicWidth()) { - setActiveLayoutWidth(activeLayoutWidth); - setOuterLayoutWidth(activeLayoutSize.getWidth()); - } - - if (isDynamicHeight()) { - setActiveLayoutHeight(activeLayoutHeight); - setOuterLayoutHeight(activeLayoutSize.getHeight()); - } - - return activeLayoutSize; - } - - private void setActiveLayoutWidth(int activeLayoutWidth) { - if (activeLayoutWidth < 0) { - activeLayoutWidth = 0; - } - activeLayoutSize.setWidth(activeLayoutWidth); - } - - private void setActiveLayoutHeight(int activeLayoutHeight) { - if (activeLayoutHeight < 0) { - activeLayoutHeight = 0; - } - activeLayoutSize.setHeight(activeLayoutHeight); - - } - - private void setOuterLayoutWidth(int activeLayoutWidth) { - // Don't call setWidth to avoid triggering all kinds of recalculations - // Also don't call super.setWidth to avoid messing with the - // dynamicWidth property - int newPixelWidth = (activeLayoutWidth + activeMargins.getHorizontal()); - getElement().getStyle().setWidth(newPixelWidth, Unit.PX); - } - - private void setOuterLayoutHeight(int activeLayoutHeight) { - // Don't call setHeight to avoid triggering all kinds of recalculations - // Also don't call super.setHeight to avoid messing with the - // dynamicHeight property - int newPixelHeight = (activeLayoutHeight + activeMargins.getVertical()); - getElement().getStyle().setHeight(newPixelHeight, Unit.PX); - } - - /** - * Updates the spacing between components. Needs to be done only when - * components are added/removed. - */ - private void updateContainerMargins() { - ChildComponentContainer firstChildComponent = getFirstChildComponentContainer(); - if (firstChildComponent != null) { - firstChildComponent.setMarginLeft(0); - firstChildComponent.setMarginTop(0); - - for (ChildComponentContainer childComponent : widgetToComponentContainer - .values()) { - if (childComponent == firstChildComponent) { - continue; - } - - if (isHorizontal()) { - childComponent.setMarginLeft(activeSpacing.hSpacing); - } else { - childComponent.setMarginTop(activeSpacing.vSpacing); - } - } - } - } - - private boolean isHorizontal() { - return orientation == ORIENTATION_HORIZONTAL; - } - - private boolean isVertical() { - return orientation == ORIENTATION_VERTICAL; - } - - private ChildComponentContainer createChildContainer(Widget child) { - - // Create a container DIV for the child - ChildComponentContainer childComponent = new ChildComponentContainer( - child, orientation); - - return childComponent; - - } - - public RenderSpace getAllocatedSpace(Widget child) { - int width = 0; - int height = 0; - ChildComponentContainer childComponentContainer = getComponentContainer(child); - // WIDTH CALCULATION - if (isVertical()) { - width = activeLayoutSize.getWidth(); - width -= childComponentContainer.getCaptionWidthAfterComponent(); - } else if (!isDynamicWidth()) { - // HORIZONTAL - width = childComponentContainer.getContSize().getWidth(); - width -= childComponentContainer.getCaptionWidthAfterComponent(); - } - - // HEIGHT CALCULATION - if (isHorizontal()) { - height = activeLayoutSize.getHeight(); - height -= childComponentContainer.getCaptionHeightAboveComponent(); - } else if (!isDynamicHeight()) { - // VERTICAL - height = childComponentContainer.getContSize().getHeight(); - height -= childComponentContainer.getCaptionHeightAboveComponent(); - } - - // ApplicationConnection.getConsole().log( - // "allocatedSpace for " + Util.getSimpleName(child) + ": " - // + width + "," + height); - RenderSpace space = new RenderSpace(width, height); - return space; - } - - private void recalculateLayoutAndComponentSizes() { - recalculateLayout(); - - if (!(isDynamicHeight() && isDynamicWidth())) { - /* First update relative sized components */ - for (ChildComponentContainer componentContainer : widgetToComponentContainer - .values()) { - client.handleComponentRelativeSize(componentContainer - .getWidget()); - - // Update widget size from DOM - componentContainer.updateWidgetSize(); - } - } - - if (isDynamicHeight()) { - /* - * Height is not necessarily correct anymore as the height of - * components might have changed if the width has changed. - */ - - /* - * Get the new widget sizes from DOM and calculate new container - * sizes - */ - updateWidgetSizes(); - - /* Update layout dimensions based on widget sizes */ - recalculateLayout(); - } - - updateRelativeSizesInNonMainDirection(); - calculateAlignments(); - - setRootSize(); - } - - private void setRootSize() { - root.getStyle().setPropertyPx("width", activeLayoutSize.getWidth()); - root.getStyle().setPropertyPx("height", activeLayoutSize.getHeight()); - } - - public boolean requestLayout(Set<Paintable> children) { - for (Paintable p : children) { - /* Update widget size from DOM */ - ChildComponentContainer componentContainer = getComponentContainer((Widget) p); - // This should no longer be needed (after #2563) - // if (isDynamicWidth()) { - // componentContainer.setUnlimitedContainerWidth(); - // } else { - // componentContainer.setLimitedContainerWidth(activeLayoutSize - // .getWidth()); - // } - - componentContainer.updateWidgetSize(); - - /* - * If this is the result of an caption icon onload event the caption - * size may have changed - */ - componentContainer.updateCaptionSize(); - } - - Size sizeBefore = new Size(activeLayoutSize.getWidth(), - activeLayoutSize.getHeight()); - - recalculateLayoutAndComponentSizes(); - boolean sameSize = (sizeBefore.equals(activeLayoutSize)); - if (!sameSize) { - /* Must inform child components about possible size updates */ - client.runDescendentsLayout(this); - } - - /* Automatically propagated upwards if the size has changed */ - - return sameSize; - } - - @Override - public void setHeight(String height) { - Size sizeBefore = new Size(activeLayoutSize.getWidth(), - activeLayoutSize.getHeight()); - - super.setHeight(height); - - if (height != null && !height.equals("")) { - setActiveLayoutHeight(getOffsetHeight() - - activeMargins.getVertical()); - } - - if (isRendering) { - sizeHasChangedDuringRendering = true; - } else { - recalculateLayoutAndComponentSizes(); - boolean sameSize = (sizeBefore.equals(activeLayoutSize)); - if (!sameSize) { - /* Must inform child components about possible size updates */ - client.runDescendentsLayout(this); - } - } - } - - @Override - public void setWidth(String width) { - if (this.width.equals(width) || !isVisible()) { - return; - } - Size sizeBefore = new Size(activeLayoutSize.getWidth(), - activeLayoutSize.getHeight()); - - super.setWidth(width); - this.width = width; - if (width != null && !width.equals("")) { - setActiveLayoutWidth(getOffsetWidth() - - activeMargins.getHorizontal()); - } - - if (isRendering) { - sizeHasChangedDuringRendering = true; - } else { - recalculateLayoutAndComponentSizes(); - boolean sameSize = (sizeBefore.equals(activeLayoutSize)); - if (!sameSize) { - /* Must inform child components about possible size updates */ - client.runDescendentsLayout(this); - } - /* - * If the height changes as a consequence of this we must inform the - * parent also - */ - if (isDynamicHeight() - && sizeBefore.getHeight() != activeLayoutSize.getHeight()) { - Util.notifyParentOfSizeChange(this, false); - } - - } - } - - protected void updateAlignmentsAndExpandRatios(UIDL uidl, - ArrayList<Widget> renderedWidgets) { - - /* - */ - alignments = uidl.getMapAttribute("alignments"); - - /* - * UIDL contains a map of paintable ids to expand ratios - */ - - expandRatios = uidl.getMapAttribute("expandRatios"); - expandRatioSum = -1.0; - - for (int i = 0; i < renderedWidgets.size(); i++) { - Widget widget = renderedWidgets.get(i); - String pid = client.getPid(widget.getElement()); - - ChildComponentContainer container = getComponentContainer(widget); - - // Calculate alignment info - container.setAlignment(getAlignment(pid)); - - // Update expand ratio - container.setNormalizedExpandRatio(getExpandRatio(pid)); - } - } - - private AlignmentInfo getAlignment(String pid) { - if (alignments.containsKey(pid)) { - return new AlignmentInfo(alignments.getInt(pid)); - } else { - return AlignmentInfo.TOP_LEFT; - } - } - - private double getExpandRatio(String pid) { - if (expandRatioSum < 0) { - expandRatioSum = 0; - JsArrayString keyArray = expandRatios.getKeyArray(); - int length = keyArray.length(); - for (int i = 0; i < length; i++) { - expandRatioSum += expandRatios.getRawNumber(keyArray.get(i)); - } - if (expandRatioSum == 0) { - // by default split equally among components - defaultExpandRatio = 1.0 / widgetToComponentContainer.size(); - } else { - defaultExpandRatio = 0; - } - } - if (expandRatios.containsKey(pid)) { - return expandRatios.getRawNumber(pid) / expandRatioSum; - } else { - return defaultExpandRatio; - } - } - - public void updateCaption(Paintable component, UIDL uidl) { - ChildComponentContainer componentContainer = getComponentContainer((Widget) component); - componentContainer.updateCaption(uidl, client); - if (!isRendering) { - /* - * This was a component-only update and the possible size change - * must be propagated to the layout - */ - client.captionSizeUpdated(component); - } - } - - /** - * Returns the deepest nested child component which contains "element". The - * child component is also returned if "element" is part of its caption. - * - * @param element - * An element that is a nested sub element of the root element in - * this layout - * @return The Paintable which the element is a part of. Null if the element - * belongs to the layout and not to a child. - */ - private Paintable getComponent(Element element) { - return Util.getPaintableForElement(client, this, element); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java b/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java index 413f30ad06..df655ef959 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java @@ -15,7 +15,6 @@ import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.RootPanel; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Util; /** * In Vaadin UI this Overlay should always be used for all elements that @@ -28,7 +27,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { * The z-index value from where all overlays live. This can be overridden in * any extending class. */ - protected static int Z_INDEX = 20000; + public static int Z_INDEX = 20000; private static int leftFix = -1; @@ -162,32 +161,18 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { private static int adjustByRelativeTopBodyMargin() { if (topFix == -1) { - boolean ie6OrIe7 = BrowserInfo.get().isIE() - && BrowserInfo.get().getIEVersion() <= 7; - topFix = detectRelativeBodyFixes("top", ie6OrIe7); + topFix = detectRelativeBodyFixes("top"); } return topFix; } - private native static int detectRelativeBodyFixes(String axis, - boolean removeClientLeftOrTop) + private native static int detectRelativeBodyFixes(String axis) /*-{ try { var b = $wnd.document.body; var cstyle = b.currentStyle ? b.currentStyle : getComputedStyle(b); if(cstyle && cstyle.position == 'relative') { - var offset = b.getBoundingClientRect()[axis]; - if (removeClientLeftOrTop) { - // IE6 and IE7 include the top left border of the client area into the boundingClientRect - var clientTopOrLeft = 0; - if (axis == "top") - clientTopOrLeft = $wnd.document.documentElement.clientTop; - else - clientTopOrLeft = $wnd.document.documentElement.clientLeft; - - offset -= clientTopOrLeft; - } - return offset; + return b.getBoundingClientRect()[axis]; } } catch(e){} return 0; @@ -195,9 +180,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { private static int adjustByRelativeLeftBodyMargin() { if (leftFix == -1) { - boolean ie6OrIe7 = BrowserInfo.get().isIE() - && BrowserInfo.get().getIEVersion() <= 7; - leftFix = detectRelativeBodyFixes("left", ie6OrIe7); + leftFix = detectRelativeBodyFixes("left"); } return leftFix; @@ -214,13 +197,6 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { updateShadowSizeAndPosition(1.0); } } - Util.runIE7ZeroSizedBodyFix(); - } - - @Override - public void hide(boolean autoClosed) { - super.hide(autoClosed); - Util.runIE7ZeroSizedBodyFix(); } @Override @@ -273,7 +249,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { * size of overlay without using normal 'setWidth(String)' and * 'setHeight(String)' methods (if not calling super.setWidth/Height). */ - protected void updateShadowSizeAndPosition() { + public void updateShadowSizeAndPosition() { updateShadowSizeAndPosition(1.0); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VPanel.java deleted file mode 100644 index 036f4f0600..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VPanel.java +++ /dev/null @@ -1,609 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Set; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; - -public class VPanel extends SimplePanel implements Container, - ShortcutActionHandlerOwner, Focusable { - - public static final String CLICK_EVENT_IDENTIFIER = "click"; - public static final String CLASSNAME = "v-panel"; - - ApplicationConnection client; - - String id; - - private final Element captionNode = DOM.createDiv(); - - private final Element captionText = DOM.createSpan(); - - private Icon icon; - - private final Element bottomDecoration = DOM.createDiv(); - - private final Element contentNode = DOM.createDiv(); - - private Element errorIndicatorElement; - - private String height; - - private Paintable layout; - - ShortcutActionHandler shortcutHandler; - - private String width = ""; - - private Element geckoCaptionMeter; - - private int scrollTop; - - private int scrollLeft; - - private RenderInformation renderInformation = new RenderInformation(); - - private int borderPaddingHorizontal = -1; - - private int borderPaddingVertical = -1; - - private int captionPaddingHorizontal = -1; - - private int captionMarginLeft = -1; - - private boolean rendering; - - private int contentMarginLeft = -1; - - private String previousStyleName; - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this, - CLICK_EVENT_IDENTIFIER) { - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - }; - private TouchScrollDelegate touchScrollDelegate; - - public VPanel() { - super(); - DivElement captionWrap = Document.get().createDivElement(); - captionWrap.appendChild(captionNode); - captionNode.appendChild(captionText); - - captionWrap.setClassName(CLASSNAME + "-captionwrap"); - captionNode.setClassName(CLASSNAME + "-caption"); - contentNode.setClassName(CLASSNAME + "-content"); - bottomDecoration.setClassName(CLASSNAME + "-deco"); - - getElement().appendChild(captionWrap); - - /* - * Make contentNode focusable only by using the setFocus() method. This - * behaviour can be changed by invoking setTabIndex() in the serverside - * implementation - */ - contentNode.setTabIndex(-1); - - getElement().appendChild(contentNode); - - getElement().appendChild(bottomDecoration); - setStyleName(CLASSNAME); - DOM.sinkEvents(getElement(), Event.ONKEYDOWN); - DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); - contentNode.getStyle().setProperty("position", "relative"); - getElement().getStyle().setProperty("overflow", "hidden"); - addHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); - } - - /** - * Sets the keyboard focus on the Panel - * - * @param focus - * Should the panel have focus or not. - */ - public void setFocus(boolean focus) { - if (focus) { - getContainerElement().focus(); - } else { - getContainerElement().blur(); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.Focusable#focus() - */ - public void focus() { - setFocus(true); - - } - - @Override - protected Element getContainerElement() { - return contentNode; - } - - private void setCaption(String text) { - DOM.setInnerHTML(captionText, text); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - if (!uidl.hasAttribute("cached")) { - - // Handle caption displaying and style names, prior generics. - // Affects size - // calculations - - // Restore default stylenames - contentNode.setClassName(CLASSNAME + "-content"); - bottomDecoration.setClassName(CLASSNAME + "-deco"); - captionNode.setClassName(CLASSNAME + "-caption"); - boolean hasCaption = false; - if (uidl.hasAttribute("caption") - && !uidl.getStringAttribute("caption").equals("")) { - setCaption(uidl.getStringAttribute("caption")); - hasCaption = true; - } else { - setCaption(""); - captionNode.setClassName(CLASSNAME + "-nocaption"); - } - - // Add proper stylenames for all elements. This way we can prevent - // unwanted CSS selector inheritance. - if (uidl.hasAttribute("style")) { - final String[] styles = uidl.getStringAttribute("style").split( - " "); - final String captionBaseClass = CLASSNAME - + (hasCaption ? "-caption" : "-nocaption"); - final String contentBaseClass = CLASSNAME + "-content"; - final String decoBaseClass = CLASSNAME + "-deco"; - String captionClass = captionBaseClass; - String contentClass = contentBaseClass; - String decoClass = decoBaseClass; - for (int i = 0; i < styles.length; i++) { - captionClass += " " + captionBaseClass + "-" + styles[i]; - contentClass += " " + contentBaseClass + "-" + styles[i]; - decoClass += " " + decoBaseClass + "-" + styles[i]; - } - captionNode.setClassName(captionClass); - contentNode.setClassName(contentClass); - bottomDecoration.setClassName(decoClass); - - } - } - // Ensure correct implementation - if (client.updateComponent(this, uidl, false)) { - rendering = false; - return; - } - - clickEventHandler.handleEventHandlerRegistration(client); - - this.client = client; - id = uidl.getId(); - - setIconUri(uidl, client); - - handleError(uidl); - - // Render content - final UIDL layoutUidl = uidl.getChildUIDL(0); - final Paintable newLayout = client.getPaintable(layoutUidl); - if (newLayout != layout) { - if (layout != null) { - client.unregisterPaintable(layout); - } - setWidget((Widget) newLayout); - layout = newLayout; - } - layout.updateFromUIDL(layoutUidl, client); - - // We may have actions attached to this panel - if (uidl.getChildCount() > 1) { - final int cnt = uidl.getChildCount(); - for (int i = 1; i < cnt; i++) { - UIDL childUidl = uidl.getChildUIDL(i); - if (childUidl.getTag().equals("actions")) { - if (shortcutHandler == null) { - shortcutHandler = new ShortcutActionHandler(id, client); - } - shortcutHandler.updateActionMap(childUidl); - } - } - } - - if (uidl.hasVariable("scrollTop") - && uidl.getIntVariable("scrollTop") != scrollTop) { - scrollTop = uidl.getIntVariable("scrollTop"); - contentNode.setScrollTop(scrollTop); - // re-read the actual scrollTop in case invalid value was set - // (scrollTop != 0 when no scrollbar exists, other values would be - // caught by scroll listener), see #3784 - scrollTop = contentNode.getScrollTop(); - } - - if (uidl.hasVariable("scrollLeft") - && uidl.getIntVariable("scrollLeft") != scrollLeft) { - scrollLeft = uidl.getIntVariable("scrollLeft"); - contentNode.setScrollLeft(scrollLeft); - // re-read the actual scrollTop in case invalid value was set - // (scrollTop != 0 when no scrollbar exists, other values would be - // caught by scroll listener), see #3784 - scrollLeft = contentNode.getScrollLeft(); - } - - // Must be run after scrollTop is set as Webkit overflow fix re-sets the - // scrollTop - runHacks(false); - - // And apply tab index - if (uidl.hasVariable("tabindex")) { - contentNode.setTabIndex(uidl.getIntVariable("tabindex")); - } - - rendering = false; - - } - - @Override - public void setStyleName(String style) { - if (!style.equals(previousStyleName)) { - super.setStyleName(style); - detectContainerBorders(); - previousStyleName = style; - } - } - - private void handleError(UIDL uidl) { - if (uidl.hasAttribute("error")) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createSpan(); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); - sinkEvents(Event.MOUSEEVENTS); - } - DOM.insertBefore(captionNode, errorIndicatorElement, captionText); - } else if (errorIndicatorElement != null) { - DOM.removeChild(captionNode, errorIndicatorElement); - errorIndicatorElement = null; - } - } - - private void setIconUri(UIDL uidl, ApplicationConnection client) { - final String iconUri = uidl.hasAttribute("icon") ? uidl - .getStringAttribute("icon") : null; - if (iconUri == null) { - if (icon != null) { - DOM.removeChild(captionNode, icon.getElement()); - icon = null; - } - } else { - if (icon == null) { - icon = new Icon(client); - DOM.insertChild(captionNode, icon.getElement(), 0); - } - icon.setUri(iconUri); - } - } - - public void runHacks(boolean runGeckoFix) { - if (BrowserInfo.get().isIE6() && width != null && !width.equals("")) { - /* - * IE6 requires overflow-hidden elements to have a width specified - * so we calculate the width of the content and caption nodes when - * no width has been specified. - */ - /* - * Fixes #1923 VPanel: Horizontal scrollbar does not appear in IE6 - * with wide content - */ - - /* - * Caption must be shrunk for parent measurements to return correct - * result in IE6 - */ - DOM.setStyleAttribute(captionNode, "width", "1px"); - - int parentPadding = Util.measureHorizontalPaddingAndBorder( - getElement(), 0); - - int parentWidthExcludingPadding = getElement().getOffsetWidth() - - parentPadding; - - Util.setWidthExcludingPaddingAndBorder(captionNode, - parentWidthExcludingPadding - getCaptionMarginLeft(), 26, - false); - - int contentMarginLeft = getContentMarginLeft(); - - Util.setWidthExcludingPaddingAndBorder(contentNode, - parentWidthExcludingPadding - contentMarginLeft, 2, false); - - } - - if ((BrowserInfo.get().isIE() || BrowserInfo.get().isFF2()) - && (width == null || width.equals(""))) { - /* - * IE and FF2 needs width to be specified for the root DIV so we - * calculate that from the sizes of the caption and layout - */ - int captionWidth = captionText.getOffsetWidth() - + getCaptionMarginLeft() + getCaptionPaddingHorizontal(); - int layoutWidth = ((Widget) layout).getOffsetWidth() - + getContainerBorderWidth(); - int width = layoutWidth; - if (captionWidth > width) { - width = captionWidth; - } - - if (BrowserInfo.get().isIE7()) { - Util.setWidthExcludingPaddingAndBorder(captionNode, width - - getCaptionMarginLeft(), 26, false); - } - - super.setWidth(width + "px"); - } - - if (runGeckoFix && BrowserInfo.get().isGecko()) { - // workaround for #1764 - if (width == null || width.equals("")) { - if (geckoCaptionMeter == null) { - geckoCaptionMeter = DOM.createDiv(); - DOM.appendChild(captionNode, geckoCaptionMeter); - } - int captionWidth = DOM.getElementPropertyInt(captionText, - "offsetWidth"); - int availWidth = DOM.getElementPropertyInt(geckoCaptionMeter, - "offsetWidth"); - if (captionWidth == availWidth) { - /* - * Caption width defines panel width -> Gecko based browsers - * somehow fails to float things right, without the - * "noncode" below - */ - setWidth(getOffsetWidth() + "px"); - } else { - DOM.setStyleAttribute(captionNode, "width", ""); - } - } - } - - client.runDescendentsLayout(this); - - Util.runWebkitOverflowAutoFix(contentNode); - - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - final Element target = DOM.eventGetTarget(event); - final int type = DOM.eventGetType(event); - if (type == Event.ONKEYDOWN && shortcutHandler != null) { - shortcutHandler.handleKeyboardEvent(event); - return; - } - if (type == Event.ONSCROLL) { - int newscrollTop = DOM.getElementPropertyInt(contentNode, - "scrollTop"); - int newscrollLeft = DOM.getElementPropertyInt(contentNode, - "scrollLeft"); - if (client != null - && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) { - scrollLeft = newscrollLeft; - scrollTop = newscrollTop; - client.updateVariable(id, "scrollTop", scrollTop, false); - client.updateVariable(id, "scrollLeft", scrollLeft, false); - } - } else if (captionNode.isOrHasChild(target)) { - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - } - - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(contentNode); - } - return touchScrollDelegate; - - } - - @Override - public void setHeight(String height) { - this.height = height; - super.setHeight(height); - if (height != null && !"".equals(height)) { - final int targetHeight = getOffsetHeight(); - int containerHeight = targetHeight - - captionNode.getParentElement().getOffsetHeight() - - bottomDecoration.getOffsetHeight() - - getContainerBorderHeight(); - if (containerHeight < 0) { - containerHeight = 0; - } - DOM.setStyleAttribute(contentNode, "height", containerHeight + "px"); - } else { - DOM.setStyleAttribute(contentNode, "height", ""); - } - if (!rendering) { - runHacks(true); - } - } - - private int getCaptionMarginLeft() { - if (captionMarginLeft < 0) { - detectContainerBorders(); - } - return captionMarginLeft; - } - - private int getContentMarginLeft() { - if (contentMarginLeft < 0) { - detectContainerBorders(); - } - return contentMarginLeft; - } - - private int getCaptionPaddingHorizontal() { - if (captionPaddingHorizontal < 0) { - detectContainerBorders(); - } - return captionPaddingHorizontal; - } - - private int getContainerBorderHeight() { - if (borderPaddingVertical < 0) { - detectContainerBorders(); - } - return borderPaddingVertical; - } - - @Override - public void setWidth(String width) { - if (this.width.equals(width)) { - return; - } - - this.width = width; - super.setWidth(width); - if (!rendering) { - runHacks(true); - - if (height.equals("")) { - // Width change may affect height - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this); - } - - } - } - - private int getContainerBorderWidth() { - if (borderPaddingHorizontal < 0) { - detectContainerBorders(); - } - return borderPaddingHorizontal; - } - - private void detectContainerBorders() { - DOM.setStyleAttribute(contentNode, "overflow", "hidden"); - - borderPaddingHorizontal = Util.measureHorizontalBorder(contentNode); - borderPaddingVertical = Util.measureVerticalBorder(contentNode); - - DOM.setStyleAttribute(contentNode, "overflow", "auto"); - - captionPaddingHorizontal = Util.measureHorizontalPaddingAndBorder( - captionNode, 26); - - captionMarginLeft = Util.measureMarginLeft(captionNode); - contentMarginLeft = Util.measureMarginLeft(contentNode); - - } - - public boolean hasChildComponent(Widget component) { - if (component != null && component == layout) { - return true; - } else { - return false; - } - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - // TODO This is untested as no layouts require this - if (oldComponent != layout) { - return; - } - - setWidget(newComponent); - layout = (Paintable) newComponent; - } - - public RenderSpace getAllocatedSpace(Widget child) { - int w = 0; - int h = 0; - - if (width != null && !width.equals("")) { - w = getOffsetWidth() - getContainerBorderWidth(); - if (w < 0) { - w = 0; - } - } - - if (height != null && !height.equals("")) { - h = contentNode.getOffsetHeight() - getContainerBorderHeight(); - if (h < 0) { - h = 0; - } - } - - return new RenderSpace(w, h, true); - } - - public boolean requestLayout(Set<Paintable> child) { - // content size change might cause change to its available space - // (scrollbars) - client.handleComponentRelativeSize((Widget) layout); - if (height != null && height != "" && width != null && width != "") { - /* - * If the height and width has been specified the child components - * cannot make the size of the layout change - */ - return true; - } - runHacks(false); - return !renderInformation.updateSize(getElement()); - } - - public void updateCaption(Paintable component, UIDL uidl) { - // NOP: layouts caption, errors etc not rendered in Panel - } - - @Override - protected void onAttach() { - super.onAttach(); - detectContainerBorders(); - } - - public ShortcutActionHandler getShortcutActionHandler() { - return shortcutHandler; - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java deleted file mode 100644 index ff1e2e6b78..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java +++ /dev/null @@ -1,12 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -public class VSplitPanelHorizontal extends VSplitPanel { - - public VSplitPanelHorizontal() { - super(VSplitPanel.ORIENTATION_HORIZONTAL); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java deleted file mode 100644 index dcf7622e50..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java +++ /dev/null @@ -1,12 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -public class VSplitPanelVertical extends VSplitPanel { - - public VSplitPanelVertical() { - super(VSplitPanel.ORIENTATION_VERTICAL); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTablePaging.java b/src/com/vaadin/terminal/gwt/client/ui/VTablePaging.java deleted file mode 100644 index d3cb3130b0..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTablePaging.java +++ /dev/null @@ -1,446 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Grid; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.VerticalPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; - -/** - * TODO make this work (just an early prototype). We may want to have paging - * style table which will be much lighter than VScrollTable is. - */ -public class VTablePaging extends Composite implements Table, Paintable, - ClickHandler { - - private final Grid tBody = new Grid(); - private final Button nextPage = new Button(">"); - private final Button prevPage = new Button("<"); - private final Button firstPage = new Button("<<"); - private final Button lastPage = new Button(">>"); - - private int pageLength = 15; - - private boolean rowHeaders = false; - - private ApplicationConnection client; - private String id; - - private boolean immediate = false; - - private int selectMode = Table.SELECT_MODE_NONE; - - private final ArrayList<String> selectedRowKeys = new ArrayList<String>(); - - private int totalRows; - - private final HashMap<?, ?> visibleColumns = new HashMap<Object, Object>(); - - private int rows; - - private int firstRow; - private boolean sortAscending = true; - private final HorizontalPanel pager; - - public HashMap<String, TableRow> rowKeysToTableRows = new HashMap<String, TableRow>(); - - public VTablePaging() { - - tBody.setStyleName("itable-tbody"); - - final VerticalPanel panel = new VerticalPanel(); - - pager = new HorizontalPanel(); - pager.add(firstPage); - firstPage.addClickHandler(this); - pager.add(prevPage); - prevPage.addClickHandler(this); - pager.add(nextPage); - nextPage.addClickHandler(this); - pager.add(lastPage); - lastPage.addClickHandler(this); - - panel.add(pager); - panel.add(tBody); - - initWidget(panel); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, true)) { - return; - } - - this.client = client; - id = uidl.getStringAttribute("id"); - immediate = uidl.getBooleanAttribute("immediate"); - totalRows = uidl.getIntAttribute("totalrows"); - pageLength = uidl.getIntAttribute("pagelength"); - firstRow = uidl.getIntAttribute("firstrow"); - rows = uidl.getIntAttribute("rows"); - - if (uidl.hasAttribute("selectmode")) { - if (uidl.getStringAttribute("selectmode").equals("multi")) { - selectMode = Table.SELECT_MODE_MULTI; - } else { - selectMode = Table.SELECT_MODE_SINGLE; - } - - if (uidl.hasAttribute("selected")) { - final Set<String> selectedKeys = uidl - .getStringArrayVariableAsSet("selected"); - selectedRowKeys.clear(); - for (final Iterator<String> it = selectedKeys.iterator(); it - .hasNext();) { - selectedRowKeys.add(it.next()); - } - } - } - - if (uidl.hasVariable("sortascending")) { - sortAscending = uidl.getBooleanVariable("sortascending"); - } - - if (uidl.hasAttribute("rowheaders")) { - rowHeaders = true; - } - - UIDL rowData = null; - UIDL visibleColumns = null; - for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) { - final UIDL c = (UIDL) it.next(); - if (c.getTag().equals("rows")) { - rowData = c; - } else if (c.getTag().equals("actions")) { - updateActionMap(c); - } else if (c.getTag().equals("visiblecolumns")) { - visibleColumns = c; - } - } - tBody.resize(rows + 1, uidl.getIntAttribute("cols") - + (rowHeaders ? 1 : 0)); - updateHeader(visibleColumns); - updateBody(rowData); - - updatePager(); - } - - private void updateHeader(UIDL c) { - final Iterator<?> it = c.getChildIterator(); - visibleColumns.clear(); - int colIndex = (rowHeaders ? 1 : 0); - while (it.hasNext()) { - final UIDL col = (UIDL) it.next(); - final String cid = col.getStringAttribute("cid"); - if (!col.hasAttribute("collapsed")) { - tBody.setWidget(0, colIndex, - new HeaderCell(cid, col.getStringAttribute("caption"))); - - } - colIndex++; - } - } - - private void updateActionMap(UIDL c) { - // TODO Auto-generated method stub - - } - - /** - * Updates row data from uidl. UpdateFromUIDL delegates updating tBody to - * this method. - * - * Updates may be to different part of tBody, depending on update type. It - * can be initial row data, scroll up, scroll down... - * - * @param uidl - * which contains row data - */ - private void updateBody(UIDL uidl) { - final Iterator<?> it = uidl.getChildIterator(); - - int curRowIndex = 1; - while (it.hasNext()) { - final UIDL rowUidl = (UIDL) it.next(); - final TableRow row = new TableRow(curRowIndex, - String.valueOf(rowUidl.getIntAttribute("key")), - rowUidl.hasAttribute("selected")); - int colIndex = 0; - if (rowHeaders) { - tBody.setWidget(curRowIndex, colIndex, new BodyCell(row, - rowUidl.getStringAttribute("caption"))); - colIndex++; - } - final Iterator<?> cells = rowUidl.getChildIterator(); - while (cells.hasNext()) { - final Object cell = cells.next(); - if (cell instanceof String) { - tBody.setWidget(curRowIndex, colIndex, new BodyCell(row, - (String) cell)); - } else { - final Paintable cellContent = client - .getPaintable((UIDL) cell); - final BodyCell bodyCell = new BodyCell(row); - bodyCell.setWidget((Widget) cellContent); - tBody.setWidget(curRowIndex, colIndex, bodyCell); - } - colIndex++; - } - curRowIndex++; - } - } - - private void updatePager() { - if (pageLength == 0) { - pager.setVisible(false); - return; - } - if (isFirstPage()) { - firstPage.setEnabled(false); - prevPage.setEnabled(false); - } else { - firstPage.setEnabled(true); - prevPage.setEnabled(true); - } - if (hasNextPage()) { - nextPage.setEnabled(true); - lastPage.setEnabled(true); - } else { - nextPage.setEnabled(false); - lastPage.setEnabled(false); - - } - } - - private boolean hasNextPage() { - if (firstRow + rows + 1 > totalRows) { - return false; - } - return true; - } - - private boolean isFirstPage() { - if (firstRow == 0) { - return true; - } - return false; - } - - public void onClick(ClickEvent event) { - Object sender = event.getSource(); - if (sender instanceof Button) { - if (sender == firstPage) { - client.updateVariable(id, "firstvisible", 0, true); - } else if (sender == nextPage) { - client.updateVariable(id, "firstvisible", - firstRow + pageLength, true); - } else if (sender == prevPage) { - int newFirst = firstRow - pageLength; - if (newFirst < 0) { - newFirst = 0; - } - client.updateVariable(id, "firstvisible", newFirst, true); - } else if (sender == lastPage) { - client.updateVariable(id, "firstvisible", totalRows - - pageLength, true); - } - } - if (sender instanceof HeaderCell) { - final HeaderCell hCell = (HeaderCell) sender; - client.updateVariable(id, "sortcolumn", hCell.getCid(), false); - client.updateVariable(id, "sortascending", (sortAscending ? false - : true), true); - } - } - - private class HeaderCell extends HTML { - - private String cid; - - public String getCid() { - return cid; - } - - public void setCid(String pid) { - cid = pid; - } - - HeaderCell(String pid, String caption) { - super(); - cid = pid; - addClickHandler(VTablePaging.this); - setText(caption); - // TODO remove debug color - DOM.setStyleAttribute(getElement(), "color", "brown"); - DOM.setStyleAttribute(getElement(), "font-weight", "bold"); - } - } - - /** - * Abstraction of table cell content. In needs to know on which row it is in - * case of context click. - * - * @author mattitahvonen - */ - public class BodyCell extends SimplePanel { - private final TableRow row; - - public BodyCell(TableRow row) { - super(); - sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT); - this.row = row; - } - - public BodyCell(TableRow row2, String textContent) { - super(); - sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT); - row = row2; - setWidget(new Label(textContent)); - } - - @Override - public void onBrowserEvent(Event event) { - System.out.println("CEll event: " + event.toString()); - switch (DOM.eventGetType(event)) { - case Event.BUTTON_RIGHT: - row.showContextMenu(event); - Window.alert("context menu un-implemented"); - DOM.eventCancelBubble(event, true); - break; - case Event.BUTTON_LEFT: - if (selectMode > Table.SELECT_MODE_NONE) { - row.toggleSelected(); - } - break; - default: - break; - } - super.onBrowserEvent(event); - } - } - - private class TableRow { - - private final String key; - private final int rowIndex; - private boolean selected = false; - - public TableRow(int rowIndex, String rowKey, boolean selected) { - rowKeysToTableRows.put(rowKey, this); - this.rowIndex = rowIndex; - key = rowKey; - setSelected(selected); - } - - /** - * This method is used to set row status. Does not change value on - * server. - * - * @param selected - */ - public void setSelected(boolean sel) { - selected = sel; - if (selected) { - selectedRowKeys.add(key); - DOM.setStyleAttribute( - tBody.getRowFormatter().getElement(rowIndex), - "background", "yellow"); - - } else { - selectedRowKeys.remove(key); - DOM.setStyleAttribute( - tBody.getRowFormatter().getElement(rowIndex), - "background", "transparent"); - } - } - - public void setContextMenuOptions(HashMap<?, ?> options) { - - } - - /** - * Toggles rows select state. Also updates state to server according to - * tables immediate flag. - * - */ - public void toggleSelected() { - if (selected) { - setSelected(false); - } else { - if (selectMode == Table.SELECT_MODE_SINGLE) { - deselectAll(); - } - setSelected(true); - } - client.updateVariable( - id, - "selected", - selectedRowKeys.toArray(new String[selectedRowKeys.size()]), - immediate); - } - - /** - * Shows context menu for this row. - * - * @param event - * Event which triggered context menu. Correct place for - * context menu can be determined with it. - */ - public void showContextMenu(Event event) { - System.out.println("TODO: Show context menu"); - } - } - - public void deselectAll() { - final Object[] keys = selectedRowKeys.toArray(); - for (int i = 0; i < keys.length; i++) { - final TableRow tableRow = rowKeysToTableRows.get(keys[i]); - if (tableRow != null) { - tableRow.setSelected(false); - } - } - // still ensure all selects are removed from - selectedRowKeys.clear(); - } - - public void add(Widget w) { - // TODO Auto-generated method stub - - } - - public void clear() { - // TODO Auto-generated method stub - - } - - public Iterator<Widget> iterator() { - // TODO Auto-generated method stub - return null; - } - - public boolean remove(Widget w) { - // TODO Auto-generated method stub - return false; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java deleted file mode 100644 index 7304f62f41..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java +++ /dev/null @@ -1,153 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; - -abstract class VTabsheetBase extends ComplexPanel implements Container { - - String id; - ApplicationConnection client; - - protected final ArrayList<String> tabKeys = new ArrayList<String>(); - protected int activeTabIndex = 0; - protected boolean disabled; - protected boolean readonly; - protected Set<String> disabledTabKeys = new HashSet<String>(); - protected boolean cachedUpdate = false; - - public VTabsheetBase(String classname) { - setElement(DOM.createDiv()); - setStyleName(classname); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - - // Ensure correct implementation - cachedUpdate = client.updateComponent(this, uidl, true); - if (cachedUpdate) { - return; - } - - // Update member references - id = uidl.getId(); - disabled = uidl.hasAttribute("disabled"); - - // Render content - final UIDL tabs = uidl.getChildUIDL(0); - - // Paintables in the TabSheet before update - ArrayList<Object> oldPaintables = new ArrayList<Object>(); - for (Iterator<Object> iterator = getPaintableIterator(); iterator - .hasNext();) { - oldPaintables.add(iterator.next()); - } - - // Clear previous values - tabKeys.clear(); - disabledTabKeys.clear(); - - int index = 0; - for (final Iterator<Object> it = tabs.getChildIterator(); it.hasNext();) { - final UIDL tab = (UIDL) it.next(); - final String key = tab.getStringAttribute("key"); - final boolean selected = tab.getBooleanAttribute("selected"); - final boolean hidden = tab.getBooleanAttribute("hidden"); - - if (tab.getBooleanAttribute("disabled")) { - disabledTabKeys.add(key); - } - - tabKeys.add(key); - - if (selected) { - activeTabIndex = index; - } - renderTab(tab, index, selected, hidden); - index++; - } - - int tabCount = getTabCount(); - while (tabCount-- > index) { - removeTab(index); - } - - for (int i = 0; i < getTabCount(); i++) { - Paintable p = getTab(i); - oldPaintables.remove(p); - } - - // Perform unregister for any paintables removed during update - for (Iterator<Object> iterator = oldPaintables.iterator(); iterator - .hasNext();) { - Object oldPaintable = iterator.next(); - if (oldPaintable instanceof Paintable) { - Widget w = (Widget) oldPaintable; - if (w.isAttached()) { - w.removeFromParent(); - } - client.unregisterPaintable((Paintable) oldPaintable); - } - } - - } - - /** - * @return a list of currently shown Paintables - * - * Apparently can be something else than Paintable as - * {@link #updateFromUIDL(UIDL, ApplicationConnection)} checks if - * instanceof Paintable. Therefore set to <Object> - */ - abstract protected Iterator<Object> getPaintableIterator(); - - /** - * Clears current tabs and contents - */ - abstract protected void clearPaintables(); - - /** - * Implement in extending classes. This method should render needed elements - * and set the visibility of the tab according to the 'selected' parameter. - */ - protected abstract void renderTab(final UIDL tabUidl, int index, - boolean selected, boolean hidden); - - /** - * Implement in extending classes. This method should render any previously - * non-cached content and set the activeTabIndex property to the specified - * index. - */ - protected abstract void selectTab(int index, final UIDL contentUidl); - - /** - * Implement in extending classes. This method should return the number of - * tabs currently rendered. - */ - protected abstract int getTabCount(); - - /** - * Implement in extending classes. This method should return the Paintable - * corresponding to the given index. - */ - protected abstract Paintable getTab(int index); - - /** - * Implement in extending classes. This method should remove the rendered - * tab with the specified index. - */ - protected abstract void removeTab(int index); -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java b/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java index c7442e4436..7bcdcec660 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java @@ -6,18 +6,13 @@ package com.vaadin.terminal.gwt.client.ui; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.VerticalPanel; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; import com.vaadin.terminal.gwt.client.SimpleTree; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VUIDLBrowser; -public class VUnknownComponent extends Composite implements Paintable { +public class VUnknownComponent extends Composite { com.google.gwt.user.client.ui.Label caption = new com.google.gwt.user.client.ui.Label();; SimpleTree uidlTree; - private VerticalPanel panel; - private String serverClassName = "unkwnown"; + protected VerticalPanel panel; public VUnknownComponent() { panel = new VerticalPanel(); @@ -27,32 +22,6 @@ public class VUnknownComponent extends Composite implements Paintable { caption.setStyleName("vaadin-unknown-caption"); } - public void setServerSideClassName(String serverClassName) { - this.serverClassName = serverClassName; - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, false)) { - return; - } - setCaption("Widgetset does not contain implementation for " - + serverClassName - + ". Check its @ClientWidget mapping, widgetsets " - + "GWT module description file and re-compile your" - + " widgetset. In case you have downloaded a vaadin" - + " add-on package, you might want to refer to " - + "<a href='http://vaadin.com/using-addons'>add-on " - + "instructions</a>. Unrendered UIDL:"); - if (uidlTree != null) { - uidlTree.removeFromParent(); - } - - uidlTree = new VUIDLBrowser(uidl, client.getConfiguration()); - uidlTree.open(true); - uidlTree.setText("Unrendered UIDL"); - panel.add(uidlTree); - } - public void setCaption(String c) { caption.getElement().setInnerHTML(c); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUriFragmentUtility.java b/src/com/vaadin/terminal/gwt/client/ui/VUriFragmentUtility.java deleted file mode 100644 index db01699383..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VUriFragmentUtility.java +++ /dev/null @@ -1,85 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.History; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; - -/** - * Client side implementation for UriFragmentUtility. Uses GWT's History object - * as an implementation. - * - */ -public class VUriFragmentUtility extends Widget implements Paintable, - ValueChangeHandler<String> { - - private String fragment; - private ApplicationConnection client; - private String paintableId; - private boolean immediate; - private HandlerRegistration historyValueHandlerRegistration; - - public VUriFragmentUtility() { - setElement(Document.get().createDivElement()); - if (BrowserInfo.get().isIE6()) { - getElement().getStyle().setProperty("overflow", "hidden"); - getElement().getStyle().setProperty("height", "0"); - } - } - - @Override - protected void onAttach() { - super.onAttach(); - historyValueHandlerRegistration = History.addValueChangeHandler(this); - History.fireCurrentHistoryState(); - } - - @Override - protected void onDetach() { - super.onDetach(); - historyValueHandlerRegistration.removeHandler(); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, false)) { - return; - } - String uidlFragment = uidl.getStringVariable("fragment"); - immediate = uidl.getBooleanAttribute("immediate"); - if (this.client == null) { - // initial paint has some special logic - this.client = client; - paintableId = uidl.getId(); - if (!fragment.equals(uidlFragment)) { - // initial server side fragment (from link/bookmark/typed) does - // not equal the one on - // server, send initial fragment to server - History.fireCurrentHistoryState(); - } - } else { - if (uidlFragment != null && !uidlFragment.equals(fragment)) { - fragment = uidlFragment; - // normal fragment change from server, add new history item - History.newItem(uidlFragment, false); - } - } - } - - public void onValueChange(ValueChangeEvent<String> event) { - String historyToken = event.getValue(); - fragment = historyToken; - if (client != null) { - client.updateVariable(paintableId, "fragment", fragment, immediate); - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VVerticalLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VVerticalLayout.java deleted file mode 100644 index fe5749ec8b..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VVerticalLayout.java +++ /dev/null @@ -1,14 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -public class VVerticalLayout extends VOrderedLayout { - - public static final String CLASSNAME = "v-verticallayout"; - - public VVerticalLayout() { - super(CLASSNAME, ORIENTATION_VERTICAL); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VView.java b/src/com/vaadin/terminal/gwt/client/ui/VView.java deleted file mode 100644 index 07ade6a8b1..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VView.java +++ /dev/null @@ -1,779 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.logical.shared.ResizeEvent; -import com.google.gwt.event.logical.shared.ResizeHandler; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; - -/** - * - */ -public class VView extends SimplePanel implements Container, ResizeHandler, - Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable { - - private static final String CLASSNAME = "v-view"; - - public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain"; - - private String theme; - - private Paintable layout; - - private final LinkedHashSet<VWindow> subWindows = new LinkedHashSet<VWindow>(); - - private String id; - - private ShortcutActionHandler actionHandler; - - /** stored width for IE resize optimization */ - private int width; - - /** stored height for IE resize optimization */ - private int height; - - private ApplicationConnection connection; - - /** - * We are postponing resize process with IE. IE bugs with scrollbars in some - * situations, that causes false onWindowResized calls. With Timer we will - * give IE some time to decide if it really wants to keep current size - * (scrollbars). - */ - private Timer resizeTimer; - - private int scrollTop; - - private int scrollLeft; - - private boolean rendering; - - private boolean scrollable; - - private boolean immediate; - - private boolean resizeLazy = false; - - public static final String RESIZE_LAZY = "rL"; - /** - * Reference to the parent frame/iframe. Null if there is no parent (i)frame - * or if the application and parent frame are in different domains. - */ - private Element parentFrame; - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this, - VPanel.CLICK_EVENT_IDENTIFIER) { - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - }; - - private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200, - new ScheduledCommand() { - public void execute() { - windowSizeMaybeChanged(Window.getClientWidth(), - Window.getClientHeight()); - } - - }); - - public VView() { - super(); - setStyleName(CLASSNAME); - - // Allow focusing the view by using the focus() method, the view - // should not be in the document focus flow - getElement().setTabIndex(-1); - } - - /** - * Called when the window might have been resized. - * - * @param newWidth - * The new width of the window - * @param newHeight - * The new height of the window - */ - protected void windowSizeMaybeChanged(int newWidth, int newHeight) { - boolean changed = false; - if (width != newWidth) { - width = newWidth; - changed = true; - VConsole.log("New window width: " + width); - } - if (height != newHeight) { - height = newHeight; - changed = true; - VConsole.log("New window height: " + height); - } - if (changed) { - VConsole.log("Running layout functions due to window resize"); - connection.runDescendentsLayout(VView.this); - Util.runWebkitOverflowAutoFix(getElement()); - - sendClientResized(); - } - } - - public String getTheme() { - return theme; - } - - /** - * Used to reload host page on theme changes. - */ - private static native void reloadHostPage() - /*-{ - $wnd.location.reload(); - }-*/; - - /** - * Evaluate the given script in the browser document. - * - * @param script - * Script to be executed. - */ - private static native void eval(String script) - /*-{ - try { - if (script == null) return; - $wnd.eval(script); - } catch (e) { - } - }-*/; - - /** - * Returns true if the body is NOT generated, i.e if someone else has made - * the page that we're running in. Otherwise we're in charge of the whole - * page. - * - * @return true if we're running embedded - */ - public boolean isEmbedded() { - return !getElement().getOwnerDocument().getBody().getClassName() - .contains(ApplicationConnection.GENERATED_BODY_CLASSNAME); - } - - public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { - rendering = true; - - id = uidl.getId(); - boolean firstPaint = connection == null; - connection = client; - - immediate = uidl.hasAttribute("immediate"); - resizeLazy = uidl.hasAttribute(RESIZE_LAZY); - String newTheme = uidl.getStringAttribute("theme"); - if (theme != null && !newTheme.equals(theme)) { - // Complete page refresh is needed due css can affect layout - // calculations etc - reloadHostPage(); - } else { - theme = newTheme; - } - if (uidl.hasAttribute("style")) { - setStyleName(getStylePrimaryName() + " " - + uidl.getStringAttribute("style")); - } - - if (uidl.hasAttribute("name")) { - client.setWindowName(uidl.getStringAttribute("name")); - } - - clickEventHandler.handleEventHandlerRegistration(client); - - if (!isEmbedded()) { - // only change window title if we're in charge of the whole page - com.google.gwt.user.client.Window.setTitle(uidl - .getStringAttribute("caption")); - } - - // Process children - int childIndex = 0; - - // Open URL:s - boolean isClosed = false; // was this window closed? - while (childIndex < uidl.getChildCount() - && "open".equals(uidl.getChildUIDL(childIndex).getTag())) { - final UIDL open = uidl.getChildUIDL(childIndex); - final String url = client.translateVaadinUri(open - .getStringAttribute("src")); - final String target = open.getStringAttribute("name"); - if (target == null) { - // source will be opened to this browser window, but we may have - // to finish rendering this window in case this is a download - // (and window stays open). - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - goTo(url); - } - }); - } else if ("_self".equals(target)) { - // This window is closing (for sure). Only other opens are - // relevant in this change. See #3558, #2144 - isClosed = true; - goTo(url); - } else { - String options; - if (open.hasAttribute("border")) { - if (open.getStringAttribute("border").equals("minimal")) { - options = "menubar=yes,location=no,status=no"; - } else { - options = "menubar=no,location=no,status=no"; - } - - } else { - options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes"; - } - - if (open.hasAttribute("width")) { - int w = open.getIntAttribute("width"); - options += ",width=" + w; - } - if (open.hasAttribute("height")) { - int h = open.getIntAttribute("height"); - options += ",height=" + h; - } - - Window.open(url, target, options); - } - childIndex++; - } - if (isClosed) { - // don't render the content, something else will be opened to this - // browser view - rendering = false; - return; - } - - // Draw this application level window - UIDL childUidl = uidl.getChildUIDL(childIndex); - final Paintable lo = client.getPaintable(childUidl); - - if (layout != null) { - if (layout != lo) { - // remove old - client.unregisterPaintable(layout); - // add new - setWidget((Widget) lo); - layout = lo; - } - } else { - setWidget((Widget) lo); - layout = lo; - } - - layout.updateFromUIDL(childUidl, client); - if (!childUidl.getBooleanAttribute("cached")) { - updateParentFrameSize(); - } - - // Save currently open subwindows to track which will need to be closed - final HashSet<VWindow> removedSubWindows = new HashSet<VWindow>( - subWindows); - - // Handle other UIDL children - while ((childUidl = uidl.getChildUIDL(++childIndex)) != null) { - String tag = childUidl.getTag().intern(); - if (tag == "actions") { - if (actionHandler == null) { - actionHandler = new ShortcutActionHandler(id, client); - } - actionHandler.updateActionMap(childUidl); - } else if (tag == "execJS") { - String script = childUidl.getStringAttribute("script"); - eval(script); - } else if (tag == "notifications") { - for (final Iterator<?> it = childUidl.getChildIterator(); it - .hasNext();) { - final UIDL notification = (UIDL) it.next(); - VNotification.showNotification(client, notification); - } - } else { - // subwindows - final Paintable w = client.getPaintable(childUidl); - if (subWindows.contains(w)) { - removedSubWindows.remove(w); - } else { - subWindows.add((VWindow) w); - } - w.updateFromUIDL(childUidl, client); - } - } - - // Close old windows which where not in UIDL anymore - for (final Iterator<VWindow> rem = removedSubWindows.iterator(); rem - .hasNext();) { - final VWindow w = rem.next(); - client.unregisterPaintable(w); - subWindows.remove(w); - w.hide(); - } - - if (uidl.hasAttribute("focused")) { - // set focused component when render phase is finished - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - final Paintable toBeFocused = uidl.getPaintableAttribute( - "focused", connection); - - /* - * Two types of Widgets can be focused, either implementing - * GWT HasFocus of a thinner Vaadin specific Focusable - * interface. - */ - if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) { - final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused; - toBeFocusedWidget.setFocus(true); - } else if (toBeFocused instanceof Focusable) { - ((Focusable) toBeFocused).focus(); - } else { - VConsole.log("Could not focus component"); - } - } - }); - } - - // Add window listeners on first paint, to prevent premature - // variablechanges - if (firstPaint) { - Window.addWindowClosingHandler(this); - Window.addResizeHandler(this); - } - - onResize(); - - // finally set scroll position from UIDL - if (uidl.hasVariable("scrollTop")) { - scrollable = true; - scrollTop = uidl.getIntVariable("scrollTop"); - DOM.setElementPropertyInt(getElement(), "scrollTop", scrollTop); - scrollLeft = uidl.getIntVariable("scrollLeft"); - DOM.setElementPropertyInt(getElement(), "scrollLeft", scrollLeft); - } else { - scrollable = false; - } - - // Safari workaround must be run after scrollTop is updated as it sets - // scrollTop using a deferred command. - if (BrowserInfo.get().isSafari()) { - Util.runWebkitOverflowAutoFix(getElement()); - } - - scrollIntoView(uidl); - - rendering = false; - } - - /** - * Tries to scroll paintable referenced from given UIDL snippet to be - * visible. - * - * @param uidl - */ - void scrollIntoView(final UIDL uidl) { - if (uidl.hasAttribute("scrollTo")) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - final Paintable paintable = uidl.getPaintableAttribute( - "scrollTo", connection); - ((Widget) paintable).getElement().scrollIntoView(); - } - }); - } - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - int type = DOM.eventGetType(event); - if (type == Event.ONKEYDOWN && actionHandler != null) { - actionHandler.handleKeyboardEvent(event); - return; - } else if (scrollable && type == Event.ONSCROLL) { - updateScrollPosition(); - } - } - - /** - * Updates scroll position from DOM and saves variables to server. - */ - private void updateScrollPosition() { - int oldTop = scrollTop; - int oldLeft = scrollLeft; - scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop"); - scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft"); - if (connection != null && !rendering) { - if (oldTop != scrollTop) { - connection.updateVariable(id, "scrollTop", scrollTop, false); - } - if (oldLeft != scrollLeft) { - connection.updateVariable(id, "scrollLeft", scrollLeft, false); - } - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google - * .gwt.event.logical.shared.ResizeEvent) - */ - public void onResize(ResizeEvent event) { - onResize(); - } - - /** - * Called when a resize event is received. - */ - private void onResize() { - /* - * IE (pre IE9 at least) will give us some false resize events due to - * problems with scrollbars. Firefox 3 might also produce some extra - * events. We postpone both the re-layouting and the server side event - * for a while to deal with these issues. - * - * We may also postpone these events to avoid slowness when resizing the - * browser window. Constantly recalculating the layout causes the resize - * operation to be really slow with complex layouts. - */ - boolean lazy = resizeLazy - || (BrowserInfo.get().isIE() && BrowserInfo.get() - .getIEVersion() <= 8) || BrowserInfo.get().isFF3(); - - if (lazy) { - delayedResizeExecutor.trigger(); - } else { - windowSizeMaybeChanged(Window.getClientWidth(), - Window.getClientHeight()); - } - } - - /** - * Send new dimensions to the server. - */ - private void sendClientResized() { - connection.updateVariable(id, "height", height, false); - connection.updateVariable(id, "width", width, immediate); - } - - public native static void goTo(String url) - /*-{ - $wnd.location = url; - }-*/; - - public void onWindowClosing(Window.ClosingEvent event) { - // Change focus on this window in order to ensure that all state is - // collected from textfields - // TODO this is a naive hack, that only works with text fields and may - // cause some odd issues. Should be replaced with a decent solution, see - // also related BeforeShortcutActionListener interface. Same interface - // might be usable here. - VTextField.flushChangesFromFocusedTextField(); - - // Send the closing state to server - connection.updateVariable(id, "close", true, false); - connection.sendPendingVariableChangesSync(); - } - - private final RenderSpace myRenderSpace = new RenderSpace() { - private int excessHeight = -1; - private int excessWidth = -1; - - @Override - public int getHeight() { - return getElement().getOffsetHeight() - getExcessHeight(); - } - - private int getExcessHeight() { - if (excessHeight < 0) { - detectExcessSize(); - } - return excessHeight; - } - - private void detectExcessSize() { - // TODO define that iview cannot be themed and decorations should - // get to parent element, then get rid of this expensive and error - // prone function - final String overflow = getElement().getStyle().getProperty( - "overflow"); - getElement().getStyle().setProperty("overflow", "hidden"); - if (BrowserInfo.get().isIE() - && getElement().getPropertyInt("clientWidth") == 0) { - // can't detect possibly themed border/padding width in some - // situations (with some layout configurations), use empty div - // to measure width properly - DivElement div = Document.get().createDivElement(); - div.setInnerHTML(" "); - div.getStyle().setProperty("overflow", "hidden"); - div.getStyle().setProperty("height", "1px"); - getElement().appendChild(div); - excessWidth = getElement().getOffsetWidth() - - div.getOffsetWidth(); - getElement().removeChild(div); - } else { - excessWidth = getElement().getOffsetWidth() - - getElement().getPropertyInt("clientWidth"); - } - excessHeight = getElement().getOffsetHeight() - - getElement().getPropertyInt("clientHeight"); - - getElement().getStyle().setProperty("overflow", overflow); - } - - @Override - public int getWidth() { - int w = getRealWidth(); - if (w < 10 && BrowserInfo.get().isIE7()) { - // Overcome an IE7 bug #3295 - Util.shakeBodyElement(); - w = getRealWidth(); - } - return w; - } - - private int getRealWidth() { - if (connection.getConfiguration().isStandalone()) { - return getElement().getOffsetWidth() - getExcessWidth(); - } - - // If not running standalone, there might be multiple Vaadin apps - // that won't shrink with the browser window as the components have - // calculated widths (#3125) - - // Find all Vaadin applications on the page - ArrayList<String> vaadinApps = new ArrayList<String>(); - loadAppIdListFromDOM(vaadinApps); - - // Store original styles here so they can be restored - ArrayList<String> originalDisplays = new ArrayList<String>( - vaadinApps.size()); - - String ownAppId = connection.getConfiguration().getRootPanelId(); - - // Hiding elements causes browser to forget scroll position -> must - // save values and restore when the elements are visible again #7976 - int originalScrollTop = Window.getScrollTop(); - int originalScrollLeft = Window.getScrollLeft(); - - // Set display: none for all Vaadin apps - for (int i = 0; i < vaadinApps.size(); i++) { - String appId = vaadinApps.get(i); - Element targetElement; - if (appId.equals(ownAppId)) { - // Only hide the contents of current application - targetElement = ((Widget) layout).getElement(); - } else { - // Hide everything for other applications - targetElement = Document.get().getElementById(appId); - } - Style layoutStyle = targetElement.getStyle(); - - originalDisplays.add(i, layoutStyle.getDisplay()); - layoutStyle.setDisplay(Display.NONE); - } - - int w = getElement().getOffsetWidth() - getExcessWidth(); - - // Then restore the old display style before returning - for (int i = 0; i < vaadinApps.size(); i++) { - String appId = vaadinApps.get(i); - Element targetElement; - if (appId.equals(ownAppId)) { - targetElement = ((Widget) layout).getElement(); - } else { - targetElement = Document.get().getElementById(appId); - } - Style layoutStyle = targetElement.getStyle(); - String originalDisplay = originalDisplays.get(i); - - if (originalDisplay.length() == 0) { - layoutStyle.clearDisplay(); - } else { - layoutStyle.setProperty("display", originalDisplay); - } - } - - // Scroll back to original location - Window.scrollTo(originalScrollLeft, originalScrollTop); - - return w; - } - - private int getExcessWidth() { - if (excessWidth < 0) { - detectExcessSize(); - } - return excessWidth; - } - - @Override - public int getScrollbarSize() { - return Util.getNativeScrollbarSize(); - } - }; - - private native static void loadAppIdListFromDOM(ArrayList<String> list) - /*-{ - var j; - for(j in $wnd.vaadin.vaadinConfigurations) { - list.@java.util.Collection::add(Ljava/lang/Object;)(j); - } - }-*/; - - public RenderSpace getAllocatedSpace(Widget child) { - return myRenderSpace; - } - - public boolean hasChildComponent(Widget component) { - return (component != null && component == layout); - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - // TODO This is untested as no layouts require this - if (oldComponent != layout) { - return; - } - - setWidget(newComponent); - layout = (Paintable) newComponent; - } - - public boolean requestLayout(Set<Paintable> child) { - /* - * Can never propagate further and we do not want need to re-layout the - * layout which has caused this request. - */ - updateParentFrameSize(); - - // layout size change may affect its available space (scrollbars) - connection.handleComponentRelativeSize((Widget) layout); - - return true; - - } - - private void updateParentFrameSize() { - if (parentFrame == null) { - return; - } - - int childHeight = Util.getRequiredHeight(getWidget().getElement()); - int childWidth = Util.getRequiredWidth(getWidget().getElement()); - - parentFrame.getStyle().setPropertyPx("width", childWidth); - parentFrame.getStyle().setPropertyPx("height", childHeight); - } - - private static native Element getParentFrame() - /*-{ - try { - var frameElement = $wnd.frameElement; - if (frameElement == null) { - return null; - } - if (frameElement.getAttribute("autoResize") == "true") { - return frameElement; - } - } catch (e) { - } - return null; - }-*/; - - public void updateCaption(Paintable component, UIDL uidl) { - // NOP Subwindows never draw caption for their first child (layout) - } - - /** - * Return an iterator for current subwindows. This method is meant for - * testing purposes only. - * - * @return - */ - public ArrayList<VWindow> getSubWindowList() { - ArrayList<VWindow> windows = new ArrayList<VWindow>(subWindows.size()); - for (VWindow widget : subWindows) { - windows.add(widget); - } - return windows; - } - - public void init(String rootPanelId, - ApplicationConnection applicationConnection) { - DOM.sinkEvents(getElement(), Event.ONKEYDOWN | Event.ONSCROLL); - - // iview is focused when created so element needs tabIndex - // 1 due 0 is at the end of natural tabbing order - DOM.setElementProperty(getElement(), "tabIndex", "1"); - - RootPanel root = RootPanel.get(rootPanelId); - - // Remove the v-app-loading or any splash screen added inside the div by - // the user - root.getElement().setInnerHTML(""); - // For backwards compatibility with static index pages only. - // No longer added by AbstractApplicationServlet/Portlet - root.removeStyleName("v-app-loading"); - - root.add(this); - - if (applicationConnection.getConfiguration().isStandalone()) { - // set focus to iview element by default to listen possible keyboard - // shortcuts. For embedded applications this is unacceptable as we - // don't want to steal focus from the main page nor we don't want - // side-effects from focusing (scrollIntoView). - getElement().focus(); - } - - parentFrame = getParentFrame(); - } - - public ShortcutActionHandler getShortcutActionHandler() { - return actionHandler; - } - - public void focus() { - getElement().focus(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/Vaadin6Connector.java b/src/com/vaadin/terminal/gwt/client/ui/Vaadin6Connector.java new file mode 100644 index 0000000000..7fccdafd2a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/Vaadin6Connector.java @@ -0,0 +1,17 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; + +public abstract class Vaadin6Connector extends AbstractComponentConnector + implements Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + ((Paintable) getWidget()).updateFromUIDL(uidl, client); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java new file mode 100644 index 0000000000..80b6254e02 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java @@ -0,0 +1,219 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.absolutelayout; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; +import com.vaadin.terminal.gwt.client.ui.absolutelayout.VAbsoluteLayout.AbsoluteWrapper; +import com.vaadin.ui.AbsoluteLayout; + +@Connect(AbsoluteLayout.class) +public class AbsoluteLayoutConnector extends + AbstractComponentContainerConnector implements DirectionalManagedLayout { + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return getConnectorForElement(element); + } + + @Override + protected LayoutClickRpc getLayoutClickRPC() { + return rpc; + }; + + }; + + private AbsoluteLayoutServerRpc rpc; + + private Map<String, AbsoluteWrapper> connectorIdToComponentWrapper = new HashMap<String, AbsoluteWrapper>(); + + @Override + protected void init() { + super.init(); + rpc = RpcProxy.create(AbsoluteLayoutServerRpc.class, this); + } + + /** + * Returns the deepest nested child component which contains "element". The + * child component is also returned if "element" is part of its caption. + * + * @param element + * An element that is a nested sub element of the root element in + * this layout + * @return The Paintable which the element is a part of. Null if the element + * belongs to the layout and not to a child. + */ + protected ComponentConnector getConnectorForElement(Element element) { + return Util.getConnectorForElement(getConnection(), getWidget(), + element); + } + + public void updateCaption(ComponentConnector component) { + VAbsoluteLayout absoluteLayoutWidget = getWidget(); + AbsoluteWrapper componentWrapper = getWrapper(component); + + boolean captionIsNeeded = VCaption.isNeeded(component.getState()); + + VCaption caption = componentWrapper.getCaption(); + + if (captionIsNeeded) { + if (caption == null) { + caption = new VCaption(component, getConnection()); + absoluteLayoutWidget.add(caption); + componentWrapper.setCaption(caption); + } + caption.updateCaption(); + componentWrapper.updateCaptionPosition(); + } else { + if (caption != null) { + caption.removeFromParent(); + } + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VAbsoluteLayout.class); + } + + @Override + public VAbsoluteLayout getWidget() { + return (VAbsoluteLayout) super.getWidget(); + } + + @Override + public AbsoluteLayoutState getState() { + return (AbsoluteLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + clickEventHandler.handleEventHandlerRegistration(); + + // TODO Margin handling + + for (ComponentConnector child : getChildren()) { + getWrapper(child).setPosition( + getState().getConnectorPosition(child)); + } + }; + + private AbsoluteWrapper getWrapper(ComponentConnector child) { + String childId = child.getConnectorId(); + AbsoluteWrapper wrapper = connectorIdToComponentWrapper.get(childId); + if (wrapper != null) { + return wrapper; + } + + wrapper = new AbsoluteWrapper(child.getWidget()); + connectorIdToComponentWrapper.put(childId, wrapper); + getWidget().add(wrapper); + return wrapper; + + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + for (ComponentConnector child : getChildren()) { + getWrapper(child); + } + + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() != this) { + String connectorId = oldChild.getConnectorId(); + AbsoluteWrapper absoluteWrapper = connectorIdToComponentWrapper + .remove(connectorId); + absoluteWrapper.destroy(); + } + } + } + + public void layoutVertically() { + VAbsoluteLayout layout = getWidget(); + for (ComponentConnector paintable : getChildren()) { + Widget widget = paintable.getWidget(); + AbsoluteWrapper wrapper = (AbsoluteWrapper) widget.getParent(); + Style wrapperStyle = wrapper.getElement().getStyle(); + + if (paintable.isRelativeHeight()) { + int h; + if (wrapper.top != null && wrapper.bottom != null) { + h = wrapper.getOffsetHeight(); + } else if (wrapper.bottom != null) { + // top not defined, available space 0... bottom of + // wrapper + h = wrapper.getElement().getOffsetTop() + + wrapper.getOffsetHeight(); + } else { + // top defined or both undefined, available space == + // canvas - top + h = layout.canvas.getOffsetHeight() + - wrapper.getElement().getOffsetTop(); + } + wrapperStyle.setHeight(h, Unit.PX); + getLayoutManager().reportHeightAssignedToRelative(paintable, h); + } else { + wrapperStyle.clearHeight(); + } + + wrapper.updateCaptionPosition(); + } + } + + public void layoutHorizontally() { + VAbsoluteLayout layout = getWidget(); + for (ComponentConnector paintable : getChildren()) { + AbsoluteWrapper wrapper = getWrapper(paintable); + Style wrapperStyle = wrapper.getElement().getStyle(); + + if (paintable.isRelativeWidth()) { + int w; + if (wrapper.left != null && wrapper.right != null) { + w = wrapper.getOffsetWidth(); + } else if (wrapper.right != null) { + // left == null + // available width == right edge == offsetleft + width + w = wrapper.getOffsetWidth() + + wrapper.getElement().getOffsetLeft(); + } else { + // left != null && right == null || left == null && + // right == null + // available width == canvas width - offset left + w = layout.canvas.getOffsetWidth() + - wrapper.getElement().getOffsetLeft(); + } + wrapperStyle.setWidth(w, Unit.PX); + getLayoutManager().reportWidthAssignedToRelative(paintable, w); + } else { + wrapperStyle.clearWidth(); + } + + wrapper.updateCaptionPosition(); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutServerRpc.java new file mode 100644 index 0000000000..d626eb5b6c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutServerRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.absolutelayout; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; + +public interface AbsoluteLayoutServerRpc extends LayoutClickRpc, ServerRpc { + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutState.java b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutState.java new file mode 100644 index 0000000000..4e1a43dd8b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutState.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.absolutelayout; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutState; + +public class AbsoluteLayoutState extends AbstractLayoutState { + // Maps each component to a position + private Map<String, String> connectorToCssPosition = new HashMap<String, String>(); + + public String getConnectorPosition(Connector connector) { + return connectorToCssPosition.get(connector.getConnectorId()); + } + + public Map<String, String> getConnectorToCssPosition() { + return connectorToCssPosition; + } + + public void setConnectorToCssPosition( + Map<String, String> componentToCssPosition) { + connectorToCssPosition = componentToCssPosition; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/VAbsoluteLayout.java b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/VAbsoluteLayout.java new file mode 100644 index 0000000000..e2cb629d68 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/VAbsoluteLayout.java @@ -0,0 +1,134 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.absolutelayout; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.VCaption; + +public class VAbsoluteLayout extends ComplexPanel { + + /** Tag name for widget creation */ + public static final String TAGNAME = "absolutelayout"; + + /** Class name, prefix in styling */ + public static final String CLASSNAME = "v-absolutelayout"; + + private DivElement marginElement; + + protected final Element canvas = DOM.createDiv(); + + private Object previousStyleName; + + protected ApplicationConnection client; + + public VAbsoluteLayout() { + setElement(Document.get().createDivElement()); + setStyleName(CLASSNAME); + marginElement = Document.get().createDivElement(); + canvas.getStyle().setProperty("position", "relative"); + canvas.getStyle().setProperty("overflow", "hidden"); + marginElement.appendChild(canvas); + getElement().appendChild(marginElement); + + canvas.setClassName(CLASSNAME + "-canvas"); + canvas.setClassName(CLASSNAME + "-margin"); + } + + @Override + public void add(Widget child) { + super.add(child, canvas); + } + + public static class AbsoluteWrapper extends SimplePanel { + private String css; + String left; + String top; + String right; + String bottom; + private String zIndex; + + private VCaption caption; + + public AbsoluteWrapper(Widget child) { + setWidget(child); + setStyleName(CLASSNAME + "-wrapper"); + } + + public VCaption getCaption() { + return caption; + } + + public void setCaption(VCaption caption) { + this.caption = caption; + } + + public void destroy() { + if (caption != null) { + caption.removeFromParent(); + } + removeFromParent(); + } + + public void setPosition(String stringAttribute) { + if (css == null || !css.equals(stringAttribute)) { + css = stringAttribute; + top = right = bottom = left = zIndex = null; + if (!css.equals("")) { + String[] properties = css.split(";"); + for (int i = 0; i < properties.length; i++) { + String[] keyValue = properties[i].split(":"); + if (keyValue[0].equals("left")) { + left = keyValue[1]; + } else if (keyValue[0].equals("top")) { + top = keyValue[1]; + } else if (keyValue[0].equals("right")) { + right = keyValue[1]; + } else if (keyValue[0].equals("bottom")) { + bottom = keyValue[1]; + } else if (keyValue[0].equals("z-index")) { + zIndex = keyValue[1]; + } + } + } + // ensure ne values + Style style = getElement().getStyle(); + /* + * IE8 dies when nulling zIndex, even in IE7 mode. All other css + * properties (and even in older IE's) accept null values just + * fine. Assign empty string instead of null. + */ + if (zIndex != null) { + style.setProperty("zIndex", zIndex); + } else { + style.setProperty("zIndex", ""); + } + style.setProperty("top", top); + style.setProperty("left", left); + style.setProperty("right", right); + style.setProperty("bottom", bottom); + + } + updateCaptionPosition(); + } + + void updateCaptionPosition() { + if (caption != null) { + Style style = caption.getElement().getStyle(); + style.setProperty("position", "absolute"); + style.setPropertyPx("left", getElement().getOffsetLeft()); + style.setPropertyPx("top", getElement().getOffsetTop() + - caption.getHeight()); + } + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/accordion/AccordionConnector.java b/src/com/vaadin/terminal/gwt/client/ui/accordion/AccordionConnector.java new file mode 100644 index 0000000000..a03fa37214 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/accordion/AccordionConnector.java @@ -0,0 +1,83 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.accordion; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.accordion.VAccordion.StackItem; +import com.vaadin.terminal.gwt.client.ui.layout.MayScrollChildren; +import com.vaadin.terminal.gwt.client.ui.tabsheet.TabsheetBaseConnector; +import com.vaadin.ui.Accordion; + +@Connect(Accordion.class) +public class AccordionConnector extends TabsheetBaseConnector implements + SimpleManagedLayout, MayScrollChildren { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().selectedUIDLItemIndex = -1; + super.updateFromUIDL(uidl, client); + /* + * Render content after all tabs have been created and we know how large + * the content area is + */ + if (getWidget().selectedUIDLItemIndex >= 0) { + StackItem selectedItem = getWidget().getStackItem( + getWidget().selectedUIDLItemIndex); + UIDL selectedTabUIDL = getWidget().lazyUpdateMap + .remove(selectedItem); + getWidget().open(getWidget().selectedUIDLItemIndex); + + selectedItem.setContent(selectedTabUIDL); + } else if (isRealUpdate(uidl) && getWidget().openTab != null) { + getWidget().close(getWidget().openTab); + } + + getWidget().iLayout(); + // finally render possible hidden tabs + if (getWidget().lazyUpdateMap.size() > 0) { + for (Iterator iterator = getWidget().lazyUpdateMap.keySet() + .iterator(); iterator.hasNext();) { + StackItem item = (StackItem) iterator.next(); + item.setContent(getWidget().lazyUpdateMap.get(item)); + } + getWidget().lazyUpdateMap.clear(); + } + + } + + @Override + public VAccordion getWidget() { + return (VAccordion) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VAccordion.class); + } + + public void updateCaption(ComponentConnector component) { + /* Accordion does not render its children's captions */ + } + + public void layout() { + VAccordion accordion = getWidget(); + + accordion.updateOpenTabSize(); + + if (isUndefinedHeight()) { + accordion.openTab.setHeightFromWidget(); + } + accordion.iLayout(); + + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java b/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java index 4d0776a5f9..dcd520bbb3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java +++ b/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java @@ -1,7 +1,7 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.accordion; import java.util.HashMap; import java.util.HashSet; @@ -15,80 +15,28 @@ import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ContainerResizedListener; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation; -import com.vaadin.terminal.gwt.client.RenderSpace; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.tabsheet.TabsheetBaseConnector; +import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheetBase; -public class VAccordion extends VTabsheetBase implements - ContainerResizedListener { +public class VAccordion extends VTabsheetBase { public static final String CLASSNAME = "v-accordion"; - private Set<Paintable> paintables = new HashSet<Paintable>(); + private Set<Widget> widgets = new HashSet<Widget>(); - private String height; + HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>(); - private String width = ""; + StackItem openTab = null; - private HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>(); - - private RenderSpace renderSpace = new RenderSpace(0, 0, true); - - private StackItem openTab = null; - - private boolean rendering = false; - - private int selectedUIDLItemIndex = -1; - - private RenderInformation renderInformation = new RenderInformation(); + int selectedUIDLItemIndex = -1; public VAccordion() { super(CLASSNAME); - // IE6 needs this to calculate offsetHeight correctly - if (BrowserInfo.get().isIE6()) { - DOM.setStyleAttribute(getElement(), "zoom", "1"); - } - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - selectedUIDLItemIndex = -1; - super.updateFromUIDL(uidl, client); - /* - * Render content after all tabs have been created and we know how large - * the content area is - */ - if (selectedUIDLItemIndex >= 0) { - StackItem selectedItem = getStackItem(selectedUIDLItemIndex); - UIDL selectedTabUIDL = lazyUpdateMap.remove(selectedItem); - open(selectedUIDLItemIndex); - - selectedItem.setContent(selectedTabUIDL); - } else if (!uidl.getBooleanAttribute("cached") && openTab != null) { - close(openTab); - } - - iLayout(); - // finally render possible hidden tabs - if (lazyUpdateMap.size() > 0) { - for (Iterator iterator = lazyUpdateMap.keySet().iterator(); iterator - .hasNext();) { - StackItem item = (StackItem) iterator.next(); - item.setContent(lazyUpdateMap.get(item)); - } - lazyUpdateMap.clear(); - } - - renderInformation.updateSize(getElement()); - - rendering = false; } @Override @@ -137,7 +85,7 @@ public class VAccordion extends VTabsheetBase implements private StackItem moveStackItemIfNeeded(StackItem item, int newIndex, UIDL tabUidl) { UIDL tabContentUIDL = null; - Paintable tabContent = null; + ComponentConnector tabContent = null; if (tabUidl.getChildCount() > 0) { tabContentUIDL = tabUidl.getChildUIDL(0); tabContent = client.getPaintable(tabContentUIDL); @@ -179,14 +127,13 @@ public class VAccordion extends VTabsheetBase implements // StackItem Widget oldWidget = item.getComponent(); if (oldWidget != null) { - item = new StackItem(tabUidl); - insert(item, getElement(), newIndex, true); + oldWidget.removeFromParent(); } } return item; } - private void open(int itemIndex) { + void open(int itemIndex) { StackItem item = (StackItem) getWidget(itemIndex); boolean alreadyOpen = false; if (openTab != null) { @@ -209,7 +156,7 @@ public class VAccordion extends VTabsheetBase implements updateOpenTabSize(); } - private void close(StackItem item) { + void close(StackItem item) { if (!item.isOpen()) { return; } @@ -242,58 +189,19 @@ public class VAccordion extends VTabsheetBase implements } } - @Override - public void setWidth(String width) { - if (this.width.equals(width)) { - return; - } - - Util.setWidthExcludingPaddingAndBorder(this, width, 2); - this.width = width; - if (!rendering) { - updateOpenTabSize(); - - if (isDynamicHeight()) { - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, - openTab, this); - updateOpenTabSize(); - } - - if (isDynamicHeight()) { - openTab.setHeightFromWidget(); - } - iLayout(); - } - } - - @Override - public void setHeight(String height) { - Util.setHeightExcludingPaddingAndBorder(this, height, 2); - this.height = height; - - if (!rendering) { - updateOpenTabSize(); - } - - } - /** * Sets the size of the open tab */ - private void updateOpenTabSize() { + void updateOpenTabSize() { if (openTab == null) { - renderSpace.setHeight(0); - renderSpace.setWidth(0); return; } // WIDTH if (!isDynamicWidth()) { - int w = getOffsetWidth(); - openTab.setWidth(w); - renderSpace.setWidth(w); + openTab.setWidth("100%"); } else { - renderSpace.setWidth(0); + openTab.setWidth(null); } // HEIGHT @@ -317,10 +225,8 @@ public class VAccordion extends VTabsheetBase implements spaceForOpenItem = 0; } - renderSpace.setHeight(spaceForOpenItem); openTab.setHeight(spaceForOpenItem); } else { - renderSpace.setHeight(0); openTab.setHeightFromWidget(); } @@ -348,13 +254,11 @@ public class VAccordion extends VTabsheetBase implements super.setWidth(maxWidth + "px"); openTab.setWidth(maxWidth); } - - Util.runWebkitOverflowAutoFix(openTab.getContainerElement()); - } /** - * + * A StackItem has always two children, Child 0 is a VCaption, Child 1 is + * the actual child widget. */ protected class StackItem extends ComplexPanel implements ClickHandler { @@ -383,12 +287,12 @@ public class VAccordion extends VTabsheetBase implements } public void setHeightFromWidget() { - Widget paintable = getPaintable(); - if (paintable == null) { + Widget widget = getChildWidget(); + if (widget == null) { return; } - int paintableHeight = (paintable).getElement().getOffsetHeight(); + int paintableHeight = widget.getElement().getOffsetHeight(); setHeight(paintableHeight); } @@ -432,12 +336,8 @@ public class VAccordion extends VTabsheetBase implements public StackItem(UIDL tabUidl) { setElement(DOM.createDiv()); - caption = new VCaption(null, client); + caption = new VCaption(client); caption.addClickHandler(this); - if (BrowserInfo.get().isIE6()) { - DOM.setEventListener(captionNode, this); - DOM.sinkEvents(captionNode, Event.BUTTON_LEFT); - } super.add(caption, captionNode); DOM.appendChild(captionNode, caption.getElement()); DOM.appendChild(getElement(), captionNode); @@ -459,7 +359,7 @@ public class VAccordion extends VTabsheetBase implements return content; } - public Widget getPaintable() { + public Widget getChildWidget() { if (getWidgetCount() > 1) { return getWidget(1); } else { @@ -467,14 +367,17 @@ public class VAccordion extends VTabsheetBase implements } } - public void replacePaintable(Paintable newPntbl) { + public void replaceWidget(Widget newWidget) { if (getWidgetCount() > 1) { - client.unregisterPaintable((Paintable) getWidget(1)); - paintables.remove(getWidget(1)); + Widget oldWidget = getWidget(1); + ComponentConnector oldPaintable = ConnectorMap.get(client) + .getConnector(oldWidget); + ConnectorMap.get(client).unregisterConnector(oldPaintable); + widgets.remove(oldWidget); remove(1); } - add((Widget) newPntbl, content); - paintables.add(newPntbl); + add(newWidget, content); + widgets.add(newWidget); } public void open() { @@ -496,10 +399,6 @@ public class VAccordion extends VTabsheetBase implements removeStyleDependentName("open"); setHeight(-1); setWidth(""); - if (BrowserInfo.get().isIE6()) { - // Work around for IE6 layouting problem #3359 - getElement().getStyle().setProperty("zoom", "1"); - } open = false; } @@ -508,20 +407,21 @@ public class VAccordion extends VTabsheetBase implements } public void setContent(UIDL contentUidl) { - final Paintable newPntbl = client.getPaintable(contentUidl); - if (getPaintable() == null) { - add((Widget) newPntbl, content); - paintables.add(newPntbl); - } else if (getPaintable() != newPntbl) { - replacePaintable(newPntbl); + final ComponentConnector newPntbl = client + .getPaintable(contentUidl); + Widget newWidget = newPntbl.getWidget(); + if (getChildWidget() == null) { + add(newWidget, content); + widgets.add(newWidget); + } else if (getChildWidget() != newWidget) { + replaceWidget(newWidget); } - newPntbl.updateFromUIDL(contentUidl, client); if (contentUidl.getBooleanAttribute("cached")) { /* * The size of a cached, relative sized component must be * updated to report correct size. */ - client.handleComponentRelativeSize((Widget) newPntbl); + client.handleComponentRelativeSize(newPntbl.getWidget()); } if (isOpen() && isDynamicHeight()) { setHeightFromWidget(); @@ -533,15 +433,21 @@ public class VAccordion extends VTabsheetBase implements } public void updateCaption(UIDL uidl) { - caption.updateCaption(uidl); + // TODO need to call this because the caption does not have an owner + caption.updateCaptionWithoutOwner( + uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE), + uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ICON)); } public int getWidgetWidth() { return DOM.getFirstChild(content).getOffsetWidth(); } - public boolean contains(Paintable p) { - return (getPaintable() == p); + public boolean contains(ComponentConnector p) { + return (getChildWidget() == p.getWidget()); } public boolean isCaptionVisible() { @@ -555,80 +461,22 @@ public class VAccordion extends VTabsheetBase implements clear(); } - public boolean isDynamicHeight() { - return height == null || height.equals(""); + boolean isDynamicWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedWidth(); } - public boolean isDynamicWidth() { - return width == null || width.equals(""); + boolean isDynamicHeight() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedHeight(); } @Override @SuppressWarnings("unchecked") - protected Iterator<Object> getPaintableIterator() { - return (Iterator) paintables.iterator(); - } - - public boolean hasChildComponent(Widget component) { - if (paintables.contains(component)) { - return true; - } else { - return false; - } - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - for (Widget w : getChildren()) { - StackItem item = (StackItem) w; - if (item.getPaintable() == oldComponent) { - item.replacePaintable((Paintable) newComponent); - return; - } - } - } - - public void updateCaption(Paintable component, UIDL uidl) { - /* Accordion does not render its children's captions */ - } - - public boolean requestLayout(Set<Paintable> child) { - if (!isDynamicHeight() && !isDynamicWidth()) { - /* - * If the height and width has been specified for this container the - * child components cannot make the size of the layout change - */ - // layout size change may affect its available space (scrollbars) - for (Paintable paintable : child) { - client.handleComponentRelativeSize((Widget) paintable); - } - - return true; - } - - updateOpenTabSize(); - - if (renderInformation.updateSize(getElement())) { - /* - * Size has changed so we let the child components know about the - * new size. - */ - iLayout(); - // TODO Check if this is needed - client.runDescendentsLayout(this); - - return false; - } else { - /* - * Size has not changed so we do not need to propagate the event - * further - */ - return true; - } - - } - - public RenderSpace getAllocatedSpace(Widget child) { - return renderSpace; + protected Iterator<Widget> getWidgetIterator() { + return widgets.iterator(); } @Override @@ -643,15 +491,17 @@ public class VAccordion extends VTabsheetBase implements } @Override - protected Paintable getTab(int index) { + protected ComponentConnector getTab(int index) { if (index < getWidgetCount()) { - return (Paintable) (getStackItem(index)).getPaintable(); + Widget w = getStackItem(index); + return ConnectorMap.get(client).getConnector(w); } return null; } - private StackItem getStackItem(int index) { + StackItem getStackItem(int index) { return (StackItem) getWidget(index); } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAudio.java b/src/com/vaadin/terminal/gwt/client/ui/audio/AudioConnector.java index 1fdfaca831..d55e66dbd5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VAudio.java +++ b/src/com/vaadin/terminal/gwt/client/ui/audio/AudioConnector.java @@ -1,35 +1,29 @@ /* @VaadinApache2LicenseForJavaFiles@ */ +package com.vaadin.terminal.gwt.client.ui.audio; -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.AudioElement; -import com.google.gwt.dom.client.Document; +import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.MediaBaseConnector; +import com.vaadin.ui.Audio; -public class VAudio extends VMediaBase { - private static String CLASSNAME = "v-audio"; - - private AudioElement audio; - - public VAudio() { - audio = Document.get().createAudioElement(); - setMediaElement(audio); - setStyleName(CLASSNAME); - } +@Connect(Audio.class) +public class AudioConnector extends MediaBaseConnector { @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, true)) { + super.updateFromUIDL(uidl, client); + if (!isRealUpdate(uidl)) { return; } - super.updateFromUIDL(uidl, client); - Style style = audio.getStyle(); + Style style = getWidget().getElement().getStyle(); // Make sure that the controls are not clipped if visible. if (shouldShowControls(uidl) @@ -43,7 +37,8 @@ public class VAudio extends VMediaBase { } @Override - protected String getDefaultAltHtml() { - return "Your browser does not support the <code>audio</code> element."; + protected Widget createWidget() { + return GWT.create(VAudio.class); } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/audio/VAudio.java b/src/com/vaadin/terminal/gwt/client/ui/audio/VAudio.java new file mode 100644 index 0000000000..7d5d1fe034 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/audio/VAudio.java @@ -0,0 +1,27 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.audio; + +import com.google.gwt.dom.client.AudioElement; +import com.google.gwt.dom.client.Document; +import com.vaadin.terminal.gwt.client.ui.VMediaBase; + +public class VAudio extends VMediaBase { + private static String CLASSNAME = "v-audio"; + + private AudioElement audio; + + public VAudio() { + audio = Document.get().createAudioElement(); + setMediaElement(audio); + setStyleName(CLASSNAME); + } + + @Override + protected String getDefaultAltHtml() { + return "Your browser does not support the <code>audio</code> element."; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java new file mode 100644 index 0000000000..62a5e8ac8b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java @@ -0,0 +1,134 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.button; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.EventHelper; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; +import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.ui.Button; + +@Connect(value = Button.class, loadStyle = LoadStyle.EAGER) +public class ButtonConnector extends AbstractComponentConnector implements + BlurHandler, FocusHandler, ClickHandler { + + private ButtonServerRpc rpc = RpcProxy.create(ButtonServerRpc.class, this); + private FocusAndBlurServerRpc focusBlurProxy = RpcProxy.create( + FocusAndBlurServerRpc.class, this); + + private HandlerRegistration focusHandlerRegistration = null; + private HandlerRegistration blurHandlerRegistration = null; + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + public void init() { + super.init(); + getWidget().addClickHandler(this); + getWidget().client = getConnection(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + focusHandlerRegistration = EventHelper.updateFocusHandler(this, + focusHandlerRegistration); + blurHandlerRegistration = EventHelper.updateBlurHandler(this, + blurHandlerRegistration); + // Set text + getWidget().setText(getState().getCaption()); + + // handle error + if (null != getState().getErrorMessage()) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createSpan(); + getWidget().errorIndicatorElement + .setClassName("v-errorindicator"); + } + getWidget().wrapper.insertBefore(getWidget().errorIndicatorElement, + getWidget().captionElement); + + } else if (getWidget().errorIndicatorElement != null) { + getWidget().wrapper.removeChild(getWidget().errorIndicatorElement); + getWidget().errorIndicatorElement = null; + } + + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(getConnection()); + getWidget().wrapper.insertBefore(getWidget().icon.getElement(), + getWidget().captionElement); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + } else { + if (getWidget().icon != null) { + getWidget().wrapper.removeChild(getWidget().icon.getElement()); + getWidget().icon = null; + } + } + + getWidget().clickShortcut = getState().getClickShortcutKeyCode(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VButton.class); + } + + @Override + public VButton getWidget() { + return (VButton) super.getWidget(); + } + + @Override + public ButtonState getState() { + return (ButtonState) super.getState(); + } + + public void onFocus(FocusEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurProxy.focus(); + } + + public void onBlur(BlurEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurProxy.blur(); + } + + public void onClick(ClickEvent event) { + if (getState().isDisableOnClick()) { + getWidget().setEnabled(false); + rpc.disableOnClick(); + } + + // Add mouse details + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event.getNativeEvent(), getWidget() + .getElement()); + rpc.click(details); + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonServerRpc.java new file mode 100644 index 0000000000..4a379c9262 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonServerRpc.java @@ -0,0 +1,28 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.button; + +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +/** + * RPC interface for calls from client to server. + * + * @since 7.0 + */ +public interface ButtonServerRpc extends ServerRpc { + /** + * Button click event. + * + * @param mouseEventDetails + * serialized mouse event details + */ + public void click(MouseEventDetails mouseEventDetails); + + /** + * Indicate to the server that the client has disabled the button as a + * result of a click. + */ + public void disableOnClick(); +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java new file mode 100644 index 0000000000..f26cdae0c6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java @@ -0,0 +1,65 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.button; + +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.ui.Button; + +/** + * Shared state for Button and NativeButton. + * + * @see ComponentState + * + * @since 7.0 + */ +public class ButtonState extends ComponentState { + private boolean disableOnClick = false; + private int clickShortcutKeyCode = 0; + + /** + * Checks whether the button should be disabled on the client side on next + * click. + * + * @return true if the button should be disabled on click + */ + public boolean isDisableOnClick() { + return disableOnClick; + } + + /** + * Sets whether the button should be disabled on the client side on next + * click. + * + * @param disableOnClick + * true if the button should be disabled on click + */ + public void setDisableOnClick(boolean disableOnClick) { + this.disableOnClick = disableOnClick; + } + + /** + * Returns the key code for activating the button via a keyboard shortcut. + * + * See {@link Button#setClickShortcut(int, int...)} for more information. + * + * @return key code or 0 for none + */ + public int getClickShortcutKeyCode() { + return clickShortcutKeyCode; + } + + /** + * Sets the key code for activating the button via a keyboard shortcut. + * + * See {@link Button#setClickShortcut(int, int...)} for more information. + * + * @param clickShortcutKeyCode + * key code or 0 for none + */ + public void setClickShortcutKeyCode(int clickShortcutKeyCode) { + this.clickShortcutKeyCode = clickShortcutKeyCode; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VButton.java b/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java index 9188f7406a..f7d73d3b5e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java @@ -2,48 +2,34 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.button; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Accessibility; import com.google.gwt.user.client.ui.FocusWidget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.EventHelper; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Icon; -public class VButton extends FocusWidget implements Paintable, ClickHandler, - FocusHandler, BlurHandler { +public class VButton extends FocusWidget implements ClickHandler { public static final String CLASSNAME = "v-button"; private static final String CLASSNAME_PRESSED = "v-pressed"; - public static final String ATTR_DISABLE_ON_CLICK = "dc"; - // mouse movement is checked before synthesizing click event on mouseout protected static int MOVE_THRESHOLD = 3; protected int mousedownX = 0; protected int mousedownY = 0; - protected String id; - protected ApplicationConnection client; protected final Element wrapper = DOM.createSpan(); @@ -65,8 +51,6 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, private int tabIndex = 0; - private boolean disableOnClick = false; - /* * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton */ @@ -88,10 +72,7 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, private boolean disallowNextClick = false; private boolean isHovering; - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; - - private int clickShortcut = 0; + protected int clickShortcut = 0; public VButton() { super(DOM.createDiv()); @@ -113,64 +94,6 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, addClickHandler(this); } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - // Ensure correct implementation, - // but don't let container manage caption etc. - if (client.updateComponent(this, uidl, false)) { - return; - } - - focusHandlerRegistration = EventHelper.updateFocusHandler(this, client, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, client, - blurHandlerRegistration); - - // Save details - this.client = client; - id = uidl.getId(); - - // Set text - setText(uidl.getStringAttribute("caption")); - - disableOnClick = uidl.hasAttribute(ATTR_DISABLE_ON_CLICK); - - // handle error - if (uidl.hasAttribute("error")) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createSpan(); - errorIndicatorElement.setClassName("v-errorindicator"); - } - wrapper.insertBefore(errorIndicatorElement, captionElement); - - // Fix for IE6, IE7 - if (BrowserInfo.get().isIE6() || BrowserInfo.get().isIE7()) { - errorIndicatorElement.setInnerText(" "); - } - - } else if (errorIndicatorElement != null) { - wrapper.removeChild(errorIndicatorElement); - errorIndicatorElement = null; - } - - if (uidl.hasAttribute("icon")) { - if (icon == null) { - icon = new Icon(client); - wrapper.insertBefore(icon.getElement(), captionElement); - } - icon.setUri(uidl.getStringAttribute("icon")); - } else { - if (icon != null) { - wrapper.removeChild(icon.getElement()); - icon = null; - } - } - - if (uidl.hasAttribute("keycode")) { - clickShortcut = uidl.getIntAttribute("keycode"); - } - } - public void setText(String text) { captionElement.setInnerText(text); } @@ -250,10 +173,9 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) { removeStyleName(CLASSNAME_PRESSED); } - // Explicitly prevent IE 6 to 8 from propagating mouseup events + // Explicitly prevent IE 8 from propagating mouseup events // upward (fixes #6753) - if (BrowserInfo.get().isIE() - && BrowserInfo.get().getIEVersion() < 9) { + if (BrowserInfo.get().isIE8()) { event.stopPropagation(); } } @@ -359,23 +281,9 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, * .dom.client.ClickEvent) */ public void onClick(ClickEvent event) { - if (id == null || client == null) { - return; - } if (BrowserInfo.get().isSafari()) { VButton.this.setFocus(true); } - if (disableOnClick) { - setEnabled(false); - client.updateVariable(id, "disabledOnClick", true, false); - } - - client.updateVariable(id, "state", true, false); - - // Add mouse details - MouseEventDetails details = new MouseEventDetails( - event.getNativeEvent(), getElement()); - client.updateVariable(id, "mousedetails", details.serialize(), true); clickPending = false; } @@ -457,25 +365,6 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, } } - @Override - public void setWidth(String width) { - if (BrowserInfo.get().isIE6() || BrowserInfo.get().isIE7()) { - if (width != null && width.length() > 2) { - // Assume pixel values are always sent from - // ApplicationConnection - int w = Integer - .parseInt(width.substring(0, width.length() - 2)); - w -= getHorizontalBorderAndPaddingWidth(getElement()); - if (w < 0) { - // validity check for IE - w = 0; - } - width = w + "px"; - } - } - super.setWidth(width); - } - private static native int getHorizontalBorderAndPaddingWidth(Element elem) /*-{ // THIS METHOD IS ONLY USED FOR INTERNET EXPLORER, IT DOESN'T WORK WITH OTHERS @@ -527,12 +416,4 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, return ret; }-*/; - public void onFocus(FocusEvent arg0) { - client.updateVariable(id, EventId.FOCUS, "", true); - } - - public void onBlur(BlurEvent arg0) { - client.updateVariable(id, EventId.BLUR, "", true); - } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxConnector.java b/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxConnector.java new file mode 100644 index 0000000000..731838371f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxConnector.java @@ -0,0 +1,148 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.checkbox; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.EventHelper; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.ui.CheckBox; + +@Connect(CheckBox.class) +public class CheckBoxConnector extends AbstractFieldConnector implements + FocusHandler, BlurHandler, ClickHandler { + + private HandlerRegistration focusHandlerRegistration; + private HandlerRegistration blurHandlerRegistration; + + private CheckBoxServerRpc rpc = RpcProxy.create(CheckBoxServerRpc.class, + this); + private FocusAndBlurServerRpc focusBlurRpc = RpcProxy.create( + FocusAndBlurServerRpc.class, this); + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + protected void init() { + super.init(); + getWidget().addClickHandler(this); + getWidget().client = getConnection(); + getWidget().id = getConnectorId(); + + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + focusHandlerRegistration = EventHelper.updateFocusHandler(this, + focusHandlerRegistration); + blurHandlerRegistration = EventHelper.updateBlurHandler(this, + blurHandlerRegistration); + + if (null != getState().getErrorMessage()) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createSpan(); + getWidget().errorIndicatorElement.setInnerHTML(" "); + DOM.setElementProperty(getWidget().errorIndicatorElement, + "className", "v-errorindicator"); + DOM.appendChild(getWidget().getElement(), + getWidget().errorIndicatorElement); + DOM.sinkEvents(getWidget().errorIndicatorElement, + VTooltip.TOOLTIP_EVENTS | Event.ONCLICK); + } else { + DOM.setStyleAttribute(getWidget().errorIndicatorElement, + "display", ""); + } + } else if (getWidget().errorIndicatorElement != null) { + DOM.setStyleAttribute(getWidget().errorIndicatorElement, "display", + "none"); + } + + if (isReadOnly()) { + getWidget().setEnabled(false); + } + + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(getConnection()); + DOM.insertChild(getWidget().getElement(), + getWidget().icon.getElement(), 1); + getWidget().icon.sinkEvents(VTooltip.TOOLTIP_EVENTS); + getWidget().icon.sinkEvents(Event.ONCLICK); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + } else if (getWidget().icon != null) { + // detach icon + DOM.removeChild(getWidget().getElement(), + getWidget().icon.getElement()); + getWidget().icon = null; + } + + // Set text + getWidget().setText(getState().getCaption()); + getWidget().setValue(getState().isChecked()); + getWidget().immediate = getState().isImmediate(); + } + + @Override + public CheckBoxState getState() { + return (CheckBoxState) super.getState(); + } + + @Override + public VCheckBox getWidget() { + return (VCheckBox) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VCheckBox.class); + } + + public void onFocus(FocusEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurRpc.focus(); + } + + public void onBlur(BlurEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurRpc.blur(); + } + + public void onClick(ClickEvent event) { + if (!isEnabled()) { + return; + } + + // Add mouse details + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event.getNativeEvent(), getWidget() + .getElement()); + rpc.setChecked(getWidget().getValue(), details); + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxServerRpc.java new file mode 100644 index 0000000000..05091ff6ed --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxServerRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.checkbox; + +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public interface CheckBoxServerRpc extends ServerRpc { + public void setChecked(boolean checked, MouseEventDetails mouseEventDetails); +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxState.java b/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxState.java new file mode 100644 index 0000000000..d6d51cad36 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/checkbox/CheckBoxState.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.checkbox; + +import com.vaadin.terminal.gwt.client.AbstractFieldState; + +public class CheckBoxState extends AbstractFieldState { + private boolean checked = false; + + public boolean isChecked() { + return checked; + } + + public void setChecked(boolean checked) { + this.checked = checked; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/checkbox/VCheckBox.java b/src/com/vaadin/terminal/gwt/client/ui/checkbox/VCheckBox.java new file mode 100644 index 0000000000..fd90796ea5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/checkbox/VCheckBox.java @@ -0,0 +1,61 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.checkbox; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.Icon; + +public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements + Field { + + public static final String CLASSNAME = "v-checkbox"; + + String id; + + boolean immediate; + + ApplicationConnection client; + + Element errorIndicatorElement; + + Icon icon; + + public VCheckBox() { + setStyleName(CLASSNAME); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + Element el = DOM.getFirstChild(getElement()); + while (el != null) { + DOM.sinkEvents(el, + (DOM.getEventsSunk(el) | VTooltip.TOOLTIP_EVENTS)); + el = DOM.getNextSibling(el); + } + } + + @Override + public void onBrowserEvent(Event event) { + if (icon != null && (event.getTypeInt() == Event.ONCLICK) + && (DOM.eventGetTarget(event) == icon.getElement())) { + // Click on icon should do nothing if widget is disabled + if (isEnabled()) { + setValue(!getValue()); + } + } + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + } + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/combobox/ComboBoxConnector.java b/src/com/vaadin/terminal/gwt/client/ui/combobox/ComboBoxConnector.java new file mode 100644 index 0000000000..ee16d90b12 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/combobox/ComboBoxConnector.java @@ -0,0 +1,246 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.combobox; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.combobox.VFilterSelect.FilterSelectSuggestion; +import com.vaadin.terminal.gwt.client.ui.menubar.MenuItem; +import com.vaadin.ui.Select; + +@Connect(Select.class) +public class ComboBoxConnector extends AbstractFieldConnector implements + Paintable, SimpleManagedLayout { + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal + * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection) + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + + getWidget().readonly = isReadOnly(); + getWidget().enabled = isEnabled(); + + getWidget().tb.setEnabled(getWidget().enabled); + getWidget().updateReadOnly(); + + if (!isRealUpdate(uidl)) { + return; + } + + // Inverse logic here to make the default case (text input enabled) + // work without additional UIDL messages + boolean noTextInput = uidl + .hasAttribute(VFilterSelect.ATTR_NO_TEXT_INPUT) + && uidl.getBooleanAttribute(VFilterSelect.ATTR_NO_TEXT_INPUT); + getWidget().setTextInputEnabled(!noTextInput); + + // not a FocusWidget -> needs own tabindex handling + if (uidl.hasAttribute("tabindex")) { + getWidget().tb.setTabIndex(uidl.getIntAttribute("tabindex")); + } + + if (uidl.hasAttribute("filteringmode")) { + getWidget().filteringmode = uidl.getIntAttribute("filteringmode"); + } + + getWidget().immediate = getState().isImmediate(); + + getWidget().nullSelectionAllowed = uidl.hasAttribute("nullselect"); + + getWidget().nullSelectItem = uidl.hasAttribute("nullselectitem") + && uidl.getBooleanAttribute("nullselectitem"); + + getWidget().currentPage = uidl.getIntVariable("page"); + + if (uidl.hasAttribute("pagelength")) { + getWidget().pageLength = uidl.getIntAttribute("pagelength"); + } + + if (uidl.hasAttribute(VFilterSelect.ATTR_INPUTPROMPT)) { + // input prompt changed from server + getWidget().inputPrompt = uidl + .getStringAttribute(VFilterSelect.ATTR_INPUTPROMPT); + } else { + getWidget().inputPrompt = ""; + } + + getWidget().suggestionPopup.updateStyleNames(uidl, getState()); + + getWidget().allowNewItem = uidl.hasAttribute("allownewitem"); + getWidget().lastNewItemString = null; + + getWidget().currentSuggestions.clear(); + if (!getWidget().waitingForFilteringResponse) { + /* + * Clear the current suggestions as the server response always + * includes the new ones. Exception is when filtering, then we need + * to retain the value if the user does not select any of the + * options matching the filter. + */ + getWidget().currentSuggestion = null; + /* + * Also ensure no old items in menu. Unless cleared the old values + * may cause odd effects on blur events. Suggestions in menu might + * not necessary exist in select at all anymore. + */ + getWidget().suggestionPopup.menu.clearItems(); + + } + + final UIDL options = uidl.getChildUIDL(0); + if (uidl.hasAttribute("totalMatches")) { + getWidget().totalMatches = uidl.getIntAttribute("totalMatches"); + } else { + getWidget().totalMatches = 0; + } + + // used only to calculate minimum popup width + String captions = Util.escapeHTML(getWidget().inputPrompt); + + for (final Iterator<?> i = options.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + final FilterSelectSuggestion suggestion = getWidget().new FilterSelectSuggestion( + optionUidl); + getWidget().currentSuggestions.add(suggestion); + if (optionUidl.hasAttribute("selected")) { + if (!getWidget().waitingForFilteringResponse + || getWidget().popupOpenerClicked) { + String newSelectedOptionKey = Integer.toString(suggestion + .getOptionKey()); + if (!newSelectedOptionKey + .equals(getWidget().selectedOptionKey) + || suggestion.getReplacementString().equals( + getWidget().tb.getText())) { + // Update text field if we've got a new selection + // Also update if we've got the same text to retain old + // text selection behavior + getWidget().setPromptingOff( + suggestion.getReplacementString()); + getWidget().selectedOptionKey = newSelectedOptionKey; + } + } + getWidget().currentSuggestion = suggestion; + getWidget().setSelectedItemIcon(suggestion.getIconUri()); + } + + // Collect captions so we can calculate minimum width for textarea + if (captions.length() > 0) { + captions += "|"; + } + captions += Util.escapeHTML(suggestion.getReplacementString()); + } + + if ((!getWidget().waitingForFilteringResponse || getWidget().popupOpenerClicked) + && uidl.hasVariable("selected") + && uidl.getStringArrayVariable("selected").length == 0) { + // select nulled + if (!getWidget().waitingForFilteringResponse + || !getWidget().popupOpenerClicked) { + if (!getWidget().focused) { + /* + * client.updateComponent overwrites all styles so we must + * ALWAYS set the prompting style at this point, even though + * we think it has been set already... + */ + getWidget().prompting = false; + getWidget().setPromptingOn(); + } else { + // we have focus in field, prompting can't be set on, + // instead just clear the input + getWidget().tb.setValue(""); + } + } + getWidget().setSelectedItemIcon(null); + getWidget().selectedOptionKey = null; + } + + if (getWidget().waitingForFilteringResponse + && getWidget().lastFilter.toLowerCase().equals( + uidl.getStringVariable("filter"))) { + getWidget().suggestionPopup.showSuggestions( + getWidget().currentSuggestions, getWidget().currentPage, + getWidget().totalMatches); + getWidget().waitingForFilteringResponse = false; + if (!getWidget().popupOpenerClicked + && getWidget().selectPopupItemWhenResponseIsReceived != VFilterSelect.Select.NONE) { + // we're paging w/ arrows + if (getWidget().selectPopupItemWhenResponseIsReceived == VFilterSelect.Select.LAST) { + getWidget().suggestionPopup.menu.selectLastItem(); + } else { + getWidget().suggestionPopup.menu.selectFirstItem(); + } + + // This is used for paging so we update the keyboard selection + // variable as well. + MenuItem activeMenuItem = getWidget().suggestionPopup.menu + .getSelectedItem(); + getWidget().suggestionPopup.menu + .setKeyboardSelectedItem(activeMenuItem); + + // Update text field to contain the correct text + getWidget().setTextboxText(activeMenuItem.getText()); + getWidget().tb.setSelectionRange( + getWidget().lastFilter.length(), + activeMenuItem.getText().length() + - getWidget().lastFilter.length()); + + getWidget().selectPopupItemWhenResponseIsReceived = VFilterSelect.Select.NONE; // reset + } + if (getWidget().updateSelectionWhenReponseIsReceived) { + getWidget().suggestionPopup.menu + .doPostFilterSelectedItemAction(); + } + } + + // Calculate minumum textarea width + getWidget().suggestionPopupMinWidth = getWidget().minWidth(captions); + + getWidget().popupOpenerClicked = false; + + if (!getWidget().initDone) { + getWidget().updateRootWidth(); + } + + // Focus dependent style names are lost during the update, so we add + // them here back again + if (getWidget().focused) { + getWidget().addStyleDependentName("focus"); + } + + getWidget().initDone = true; + } + + @Override + protected Widget createWidget() { + return GWT.create(VFilterSelect.class); + } + + @Override + public VFilterSelect getWidget() { + return (VFilterSelect) super.getWidget(); + } + + public void layout() { + VFilterSelect widget = getWidget(); + if (widget.initDone) { + widget.updateRootWidth(); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java b/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java index 8362d6fbec..d29eda0d6a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.combobox; import java.util.ArrayList; import java.util.Collection; @@ -12,9 +12,9 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -46,13 +46,21 @@ import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; import com.google.gwt.user.client.ui.TextBox; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.EventId; import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.Paintable; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.menubar.MenuBar; +import com.vaadin.terminal.gwt.client.ui.menubar.MenuItem; /** * Client side implementation of the Select component. @@ -60,9 +68,8 @@ import com.vaadin.terminal.gwt.client.VTooltip; * TODO needs major refactoring (to be extensible etc) */ @SuppressWarnings("deprecation") -public class VFilterSelect extends Composite implements Paintable, Field, - KeyDownHandler, KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, - Focusable { +public class VFilterSelect extends Composite implements Field, KeyDownHandler, + KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable { /** * Represents a suggestion in the suggestion popup box @@ -154,7 +161,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, private static final String Z_INDEX = "30000"; - private final SuggestionMenu menu; + protected final SuggestionMenu menu; private final Element up = DOM.createDiv(); private final Element down = DOM.createDiv(); @@ -543,14 +550,19 @@ public class VFilterSelect extends Composite implements Paintable, Field, /** * Updates style names in suggestion popup to help theme building. + * + * @param uidl + * UIDL for the whole combo box + * @param componentState + * shared state of the combo box */ - public void updateStyleNames(UIDL uidl) { - if (uidl.hasAttribute("style")) { - setStyleName(CLASSNAME + "-suggestpopup"); - final String[] styles = uidl.getStringAttribute("style").split( - " "); - for (int i = 0; i < styles.length; i++) { - addStyleDependentName(styles[i]); + public void updateStyleNames(UIDL uidl, ComponentState componentState) { + setStyleName(CLASSNAME + "-suggestpopup"); + if (componentState.hasStyles()) { + for (String style : componentState.getStyles()) { + if (!"".equals(style)) { + addStyleDependentName(style); + } } } } @@ -762,12 +774,6 @@ public class VFilterSelect extends Composite implements Paintable, Field, } public void onLoad(LoadEvent event) { - if (BrowserInfo.get().isIE6()) { - // Ensure PNG transparency works in IE6 - Util.doIE6PngFix((Element) Element.as(event.getNativeEvent() - .getEventTarget())); - } - // Handle icon onload events to ensure shadow is resized // correctly delayedImageLoadExecutioner.trigger(); @@ -783,7 +789,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, return keyboardSelectedItem; } - private void setKeyboardSelectedItem(MenuItem firstItem) { + protected void setKeyboardSelectedItem(MenuItem firstItem) { keyboardSelectedItem = firstItem; } @@ -810,7 +816,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, /** * The text box where the filter is written */ - private final TextBox tb = new TextBox() { + protected final TextBox tb = new TextBox() { /* * (non-Javadoc) * @@ -837,7 +843,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, }; }; - private final SuggestionPopup suggestionPopup = new SuggestionPopup(); + protected final SuggestionPopup suggestionPopup = new SuggestionPopup(); /** * Used when measuring the width of the popup @@ -868,74 +874,70 @@ public class VFilterSelect extends Composite implements Paintable, Field, private final Image selectedItemIcon = new Image(); - private ApplicationConnection client; + protected ApplicationConnection client; - private String paintableId; + protected String paintableId; - private int currentPage; + protected int currentPage; /** * A collection of available suggestions (options) as received from the * server. */ - private final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<FilterSelectSuggestion>(); + protected final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<FilterSelectSuggestion>(); - private boolean immediate; + protected boolean immediate; - private String selectedOptionKey; + protected String selectedOptionKey; - private boolean waitingForFilteringResponse = false; - private boolean updateSelectionWhenReponseIsReceived = false; + protected boolean waitingForFilteringResponse = false; + protected boolean updateSelectionWhenReponseIsReceived = false; private boolean tabPressedWhenPopupOpen = false; - private boolean initDone = false; + protected boolean initDone = false; - private String lastFilter = ""; + protected String lastFilter = ""; - private enum Select { + protected enum Select { NONE, FIRST, LAST }; - private Select selectPopupItemWhenResponseIsReceived = Select.NONE; + protected Select selectPopupItemWhenResponseIsReceived = Select.NONE; /** * The current suggestion selected from the dropdown. This is one of the * values in currentSuggestions except when filtering, in this case * currentSuggestion might not be in currentSuggestions. */ - private FilterSelectSuggestion currentSuggestion; + protected FilterSelectSuggestion currentSuggestion; - private int totalMatches; - private boolean allowNewItem; - private boolean nullSelectionAllowed; - private boolean nullSelectItem; - private boolean enabled; - private boolean readonly; + protected int totalMatches; + protected boolean allowNewItem; + protected boolean nullSelectionAllowed; + protected boolean nullSelectItem; + protected boolean enabled; + protected boolean readonly; - private int filteringmode = FILTERINGMODE_OFF; + protected int filteringmode = FILTERINGMODE_OFF; // shown in unfocused empty field, disappears on focus (e.g "Search here") private static final String CLASSNAME_PROMPT = "prompt"; - private static final String ATTR_INPUTPROMPT = "prompt"; + protected static final String ATTR_INPUTPROMPT = "prompt"; public static final String ATTR_NO_TEXT_INPUT = "noInput"; - private String inputPrompt = ""; - private boolean prompting = false; + protected String inputPrompt = ""; + protected boolean prompting = false; // Set true when popupopened has been clicked. Cleared on each UIDL-update. // This handles the special case where are not filtering yet and the // selected value has changed on the server-side. See #2119 - private boolean popupOpenerClicked; - private String width = null; - private int textboxPadding = -1; - private int componentPadding = -1; - private int suggestionPopupMinWidth = 0; + protected boolean popupOpenerClicked; + protected int suggestionPopupMinWidth = 0; private int popupWidth = -1; /* * Stores the last new item string to avoid double submissions. Cleared on * uidl updates */ - private String lastNewItemString; - private boolean focused = false; - private int horizPaddingAndBorder = 2; + protected String lastNewItemString; + protected boolean focused = false; /** * If set to false, the component should not allow entering text to the @@ -950,16 +952,13 @@ public class VFilterSelect extends Composite implements Paintable, Field, selectedItemIcon.setStyleName("v-icon"); selectedItemIcon.addLoadHandler(new LoadHandler() { public void onLoad(LoadEvent event) { + if (BrowserInfo.get().isIE8()) { + // IE8 needs some help to discover it should reposition the + // text field + forceReflow(); + } updateRootWidth(); updateSelectedIconPosition(); - /* - * Workaround for an IE bug where the text is positioned below - * the icon (#3991) - */ - if (BrowserInfo.get().isIE()) { - Util.setStyleTemporarily(tb.getElement(), "paddingLeft", - "0"); - } } }); @@ -1051,206 +1050,11 @@ public class VFilterSelect extends Composite implements Paintable, Field, currentPage = page; } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal - * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - @SuppressWarnings("deprecation") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - paintableId = uidl.getId(); - this.client = client; - - readonly = uidl.hasAttribute("readonly"); - enabled = !uidl.hasAttribute("disabled"); - - tb.setEnabled(enabled); - updateReadOnly(); - - if (client.updateComponent(this, uidl, true)) { - return; - } - - // Inverse logic here to make the default case (text input enabled) - // work without additional UIDL messages - boolean noTextInput = uidl.hasAttribute(ATTR_NO_TEXT_INPUT) - && uidl.getBooleanAttribute(ATTR_NO_TEXT_INPUT); - setTextInputEnabled(!noTextInput); - - // not a FocusWidget -> needs own tabindex handling - if (uidl.hasAttribute("tabindex")) { - tb.setTabIndex(uidl.getIntAttribute("tabindex")); - } - - if (uidl.hasAttribute("filteringmode")) { - filteringmode = uidl.getIntAttribute("filteringmode"); - } - - immediate = uidl.hasAttribute("immediate"); - - nullSelectionAllowed = uidl.hasAttribute("nullselect"); - - nullSelectItem = uidl.hasAttribute("nullselectitem") - && uidl.getBooleanAttribute("nullselectitem"); - - currentPage = uidl.getIntVariable("page"); - - if (uidl.hasAttribute("pagelength")) { - pageLength = uidl.getIntAttribute("pagelength"); - } - - if (uidl.hasAttribute(ATTR_INPUTPROMPT)) { - // input prompt changed from server - inputPrompt = uidl.getStringAttribute(ATTR_INPUTPROMPT); - } else { - inputPrompt = ""; - } - - suggestionPopup.updateStyleNames(uidl); - - allowNewItem = uidl.hasAttribute("allownewitem"); - lastNewItemString = null; - - currentSuggestions.clear(); - if (!waitingForFilteringResponse) { - /* - * Clear the current suggestions as the server response always - * includes the new ones. Exception is when filtering, then we need - * to retain the value if the user does not select any of the - * options matching the filter. - */ - currentSuggestion = null; - /* - * Also ensure no old items in menu. Unless cleared the old values - * may cause odd effects on blur events. Suggestions in menu might - * not necessary exist in select at all anymore. - */ - suggestionPopup.menu.clearItems(); - - } - - final UIDL options = uidl.getChildUIDL(0); - if (uidl.hasAttribute("totalMatches")) { - totalMatches = uidl.getIntAttribute("totalMatches"); - } else { - totalMatches = 0; - } - - // used only to calculate minimum popup width - String captions = Util.escapeHTML(inputPrompt); - - for (final Iterator<?> i = options.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - final FilterSelectSuggestion suggestion = new FilterSelectSuggestion( - optionUidl); - currentSuggestions.add(suggestion); - if (optionUidl.hasAttribute("selected")) { - if (!waitingForFilteringResponse || popupOpenerClicked) { - String newSelectedOptionKey = Integer.toString(suggestion - .getOptionKey()); - if (!newSelectedOptionKey.equals(selectedOptionKey) - || suggestion.getReplacementString().equals( - tb.getText())) { - // Update text field if we've got a new selection - // Also update if we've got the same text to retain old - // text selection behavior - setPromptingOff(suggestion.getReplacementString()); - selectedOptionKey = newSelectedOptionKey; - } - } - currentSuggestion = suggestion; - setSelectedItemIcon(suggestion.getIconUri()); - } - - // Collect captions so we can calculate minimum width for textarea - if (captions.length() > 0) { - captions += "|"; - } - captions += Util.escapeHTML(suggestion.getReplacementString()); - } - - if ((!waitingForFilteringResponse || popupOpenerClicked) - && uidl.hasVariable("selected") - && uidl.getStringArrayVariable("selected").length == 0) { - // select nulled - if (!waitingForFilteringResponse || !popupOpenerClicked) { - if (!focused) { - /* - * client.updateComponent overwrites all styles so we must - * ALWAYS set the prompting style at this point, even though - * we think it has been set already... - */ - prompting = false; - setPromptingOn(); - } else { - // we have focus in field, prompting can't be set on, - // instead just clear the input - tb.setValue(""); - } - } - - setSelectedItemIcon(null); - selectedOptionKey = null; - } - - if (waitingForFilteringResponse - && lastFilter.toLowerCase().equals( - uidl.getStringVariable("filter"))) { - suggestionPopup.showSuggestions(currentSuggestions, currentPage, - totalMatches); - waitingForFilteringResponse = false; - if (!popupOpenerClicked - && selectPopupItemWhenResponseIsReceived != Select.NONE) { - // we're paging w/ arrows - if (selectPopupItemWhenResponseIsReceived == Select.LAST) { - suggestionPopup.menu.selectLastItem(); - } else { - suggestionPopup.menu.selectFirstItem(); - } - - // This is used for paging so we update the keyboard selection - // variable as well. - MenuItem activeMenuItem = suggestionPopup.menu - .getSelectedItem(); - suggestionPopup.menu.setKeyboardSelectedItem(activeMenuItem); - - // Update text field to contain the correct text - setTextboxText(activeMenuItem.getText()); - tb.setSelectionRange(lastFilter.length(), activeMenuItem - .getText().length() - lastFilter.length()); - - selectPopupItemWhenResponseIsReceived = Select.NONE; // reset - } - if (updateSelectionWhenReponseIsReceived) { - suggestionPopup.menu.doPostFilterSelectedItemAction(); - } - } - - // Calculate minumum textarea width - suggestionPopupMinWidth = minWidth(captions); - - popupOpenerClicked = false; - - if (!initDone) { - updateRootWidth(); - } - - // Focus dependent style names are lost during the update, so we add - // them here back again - if (focused) { - addStyleDependentName("focus"); - } - - initDone = true; - } - - private void updateReadOnly() { + protected void updateReadOnly() { tb.setReadOnly(readonly || !textInputEnabled); } - private void setTextInputEnabled(boolean textInputEnabled) { + protected void setTextInputEnabled(boolean textInputEnabled) { // Always update styles as they might have been overwritten if (textInputEnabled) { removeStyleDependentName(STYLE_NO_INPUT); @@ -1267,45 +1071,20 @@ public class VFilterSelect extends Composite implements Paintable, Field, } /** - * Sets the text in the text box using a deferred command if on Gecko. This - * is required for performance reasons (see #3663). + * Sets the text in the text box. * * @param text * the text to set in the text box */ - private void setTextboxText(final String text) { - if (BrowserInfo.get().isFF3()) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - tb.setText(text); - } - }); - } else { - tb.setText(text); - } - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Composite#onAttach() - */ - @Override - protected void onAttach() { - super.onAttach(); - - /* - * We need to recalculate the root width when the select is attached, so - * #2974 won't happen. - */ - updateRootWidth(); + protected void setTextboxText(final String text) { + tb.setText(text); } /** * Turns prompting on. When prompting is turned on a command prompt is shown * in the text box if nothing has been entered. */ - private void setPromptingOn() { + protected void setPromptingOn() { if (!prompting) { prompting = true; addStyleDependentName(CLASSNAME_PROMPT); @@ -1320,7 +1099,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, * @param text * The text the text box should contain. */ - private void setPromptingOff(String text) { + protected void setPromptingOff(String text) { setTextboxText(text); if (prompting) { prompting = false; @@ -1370,10 +1149,15 @@ public class VFilterSelect extends Composite implements Paintable, Field, * @param iconUri * The URI of the icon */ - private void setSelectedItemIcon(String iconUri) { + protected void setSelectedItemIcon(String iconUri) { if (iconUri == null || iconUri.length() == 0) { if (selectedItemIcon.isAttached()) { panel.remove(selectedItemIcon); + if (BrowserInfo.get().isIE8()) { + // IE8 needs some help to discover it should reposition the + // text field + forceReflow(); + } updateRootWidth(); } } else { @@ -1384,6 +1168,10 @@ public class VFilterSelect extends Composite implements Paintable, Field, } } + private void forceReflow() { + Util.setStyleTemporarily(tb.getElement(), "zoom", "1"); + } + /** * Positions the icon vertically in the middle. Should be called after the * icon has loaded @@ -1391,13 +1179,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, private void updateSelectedIconPosition() { // Position icon vertically to middle int availableHeight = 0; - if (BrowserInfo.get().isIE6()) { - getElement().getStyle().setOverflow(Overflow.HIDDEN); - availableHeight = getOffsetHeight(); - getElement().getStyle().setProperty("overflow", ""); - } else { - availableHeight = getOffsetHeight(); - } + availableHeight = getOffsetHeight(); int iconHeight = Util.getRequiredHeight(selectedItemIcon); int marginTop = (availableHeight - iconHeight) / 2; @@ -1658,7 +1440,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, /** * Calculate minimum width for FilterSelect textarea */ - private native int minWidth(String captions) + protected native int minWidth(String captions) /*-{ if(!captions || captions.length <= 0) return 0; @@ -1793,68 +1575,14 @@ public class VFilterSelect extends Composite implements Paintable, Field, tb.setFocus(true); } - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.UIObject#setWidth(java.lang.String) - */ - @Override - public void setWidth(String width) { - if (width == null || width.equals("")) { - this.width = null; - } else { - this.width = width; - } - - if (BrowserInfo.get().isIE6()) { - // Required in IE when textfield is wider than this.width - getElement().getStyle().setOverflow(Overflow.HIDDEN); - horizPaddingAndBorder = Util.setWidthExcludingPaddingAndBorder( - this, width, horizPaddingAndBorder); - getElement().getStyle().setProperty("overflow", ""); - } else { - horizPaddingAndBorder = Util.setWidthExcludingPaddingAndBorder( - this, width, horizPaddingAndBorder); - } - - if (initDone) { - updateRootWidth(); - } - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.UIObject#setHeight(java.lang.String) - */ - @Override - public void setHeight(String height) { - super.setHeight(height); - Util.setHeightExcludingPaddingAndBorder(tb, height, 3); - } - /** * Calculates the width of the select if the select has undefined width. * Should be called when the width changes or when the icon changes. */ - private void updateRootWidth() { - if (width == null) { - /* - * When the width is not specified we must specify width for root - * div so the popupopener won't wrap to the next line and also so - * the size of the combobox won't change over time. - */ - int tbWidth = Util.getRequiredWidth(tb); - - /* - * Note: iconWidth is here calculated as a negative pixel value so - * you should consider this in further calculations. - */ - int iconWidth = selectedItemIcon.isAttached() ? Util - .measureMarginLeft(tb.getElement()) - - Util.measureMarginLeft(selectedItemIcon.getElement()) : 0; - - int w = tbWidth + getPopUpOpenerWidth() + iconWidth; + protected void updateRootWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + if (paintable.isUndefinedWidth()) { /* * When the select has a undefined with we need to check that we are @@ -1863,45 +1591,37 @@ public class VFilterSelect extends Composite implements Paintable, Field, * when the popup is used to view longer items than the text box is * wide. */ + int w = Util.getRequiredWidth(this); if ((!initDone || currentPage + 1 < 0) && suggestionPopupMinWidth > w) { - setTextboxWidth(suggestionPopupMinWidth); - w = suggestionPopupMinWidth; - } else { /* - * Firefox3 has its own way of doing rendering so we need to - * specify the width for the TextField to make sure it actually - * is rendered as wide as FF3 says it is + * We want to compensate for the paddings just to preserve the + * exact size as in Vaadin 6.x, but we get here before + * MeasuredSize has been initialized. + * Util.measureHorizontalPaddingAndBorder does not work with + * border-box, so we must do this the hard way. */ - tb.setWidth((tbWidth - getTextboxPadding()) + "px"); + Style style = getElement().getStyle(); + String originalPadding = style.getPadding(); + String originalBorder = style.getBorderWidth(); + style.setPaddingLeft(0, Unit.PX); + style.setBorderWidth(0, Unit.PX); + int offset = w - Util.getRequiredWidth(this); + style.setProperty("padding", originalPadding); + style.setProperty("borderWidth", originalBorder); + + setWidth(suggestionPopupMinWidth + offset + "px"); } - super.setWidth((w) + "px"); - // Freeze the initial width, so that it won't change even if the - // icon size changes - width = w + "px"; - } else { /* - * When the width is specified we also want to explicitly specify - * widths for textbox and popupopener + * Lock the textbox width to its current value if it's not already + * locked */ - setTextboxWidth(getMainWidth() - getComponentPadding()); - - } - } - - /** - * Only use the first page popup width so the textbox will not get resized - * whenever the popup is resized. This also resolves issue where toggling - * combo box between read only and normal state makes it grow larger. - * - * @return Width of popup opener - */ - private int getPopUpOpenerWidth() { - if (popupWidth < 0) { - popupWidth = Util.getRequiredWidth(popupOpener); + if (!tb.getElement().getStyle().getWidth().endsWith("px")) { + tb.setWidth((tb.getOffsetWidth() - selectedItemIcon + .getOffsetWidth()) + "px"); + } } - return popupWidth; } /** @@ -1911,63 +1631,15 @@ public class VFilterSelect extends Composite implements Paintable, Field, * @return The width in pixels */ private int getMainWidth() { - int componentWidth; - if (BrowserInfo.get().isIE6()) { - // Required in IE when textfield is wider than this.width - getElement().getStyle().setOverflow(Overflow.HIDDEN); - componentWidth = getOffsetWidth(); - getElement().getStyle().setProperty("overflow", ""); - } else { - componentWidth = getOffsetWidth(); - } - return componentWidth; - } - - /** - * Sets the text box width in pixels. - * - * @param componentWidth - * The width of the text box in pixels - */ - private void setTextboxWidth(int componentWidth) { - int padding = getTextboxPadding(); - int iconWidth = selectedItemIcon.isAttached() ? Util - .getRequiredWidth(selectedItemIcon) : 0; - - int textboxWidth = componentWidth - padding - getPopUpOpenerWidth() - - iconWidth; - if (textboxWidth < 0) { - textboxWidth = 0; - } - tb.setWidth(textboxWidth + "px"); - } - - /** - * Gets the horizontal padding of the text box in pixels. The measurement - * includes the border width. - * - * @return The padding in pixels - */ - private int getTextboxPadding() { - if (textboxPadding < 0) { - textboxPadding = Util.measureHorizontalPaddingAndBorder( - tb.getElement(), 4); - } - return textboxPadding; + return getOffsetWidth(); } - /** - * Gets the horizontal padding of the select. The measurement includes the - * border width. - * - * @return The padding in pixels - */ - private int getComponentPadding() { - if (componentPadding < 0) { - componentPadding = Util.measureHorizontalPaddingAndBorder( - getElement(), 3); + @Override + public void setWidth(String width) { + super.setWidth(width); + if (width.length() != 0) { + tb.setWidth("100%"); } - return componentPadding; } /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java new file mode 100644 index 0000000000..7df31a8593 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java @@ -0,0 +1,169 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.csslayout; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.csslayout.VCssLayout.FlowPane; +import com.vaadin.ui.CssLayout; + +@Connect(CssLayout.class) +public class CssLayoutConnector extends AbstractLayoutConnector { + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return Util.getConnectorForElement(getConnection(), getWidget(), + element); + } + + @Override + protected LayoutClickRpc getLayoutClickRPC() { + return rpc; + }; + }; + + private CssLayoutServerRpc rpc; + + private Map<ComponentConnector, VCaption> childToCaption = new HashMap<ComponentConnector, VCaption>(); + + @Override + protected void init() { + super.init(); + rpc = RpcProxy.create(CssLayoutServerRpc.class, this); + } + + @Override + public CssLayoutState getState() { + return (CssLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().setMarginStyles( + new VMarginInfo(getState().getMarginsBitmask())); + + for (ComponentConnector child : getChildren()) { + if (!getState().getChildCss().containsKey(child)) { + continue; + } + String css = getState().getChildCss().get(child); + Style style = child.getWidget().getElement().getStyle(); + // should we remove styles also? How can we know what we have added + // as it is added directly to the child component? + String[] cssRules = css.split(";"); + for (String cssRule : cssRules) { + String parts[] = cssRule.split(":"); + if (parts.length == 2) { + style.setProperty(makeCamelCase(parts[0].trim()), + parts[1].trim()); + } + } + } + + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + clickEventHandler.handleEventHandlerRegistration(); + + int index = 0; + FlowPane cssLayoutWidgetContainer = getWidget().panel; + for (ComponentConnector child : getChildren()) { + VCaption childCaption = childToCaption.get(child); + if (childCaption != null) { + cssLayoutWidgetContainer.addOrMove(childCaption, index++); + } + cssLayoutWidgetContainer.addOrMove(child.getWidget(), index++); + } + + // Detach old child widgets and possibly their caption + for (ComponentConnector child : event.getOldChildren()) { + if (child.getParent() == this) { + // Skip current children + continue; + } + cssLayoutWidgetContainer.remove(child.getWidget()); + VCaption vCaption = childToCaption.remove(child); + if (vCaption != null) { + cssLayoutWidgetContainer.remove(vCaption); + } + } + } + + private static final String makeCamelCase(String cssProperty) { + // TODO this might be cleaner to implement with regexp + while (cssProperty.contains("-")) { + int indexOf = cssProperty.indexOf("-"); + cssProperty = cssProperty.substring(0, indexOf) + + String.valueOf(cssProperty.charAt(indexOf + 1)) + .toUpperCase() + cssProperty.substring(indexOf + 2); + } + if ("float".equals(cssProperty)) { + if (BrowserInfo.get().isIE()) { + return "styleFloat"; + } else { + return "cssFloat"; + } + } + return cssProperty; + } + + @Override + public VCssLayout getWidget() { + return (VCssLayout) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VCssLayout.class); + } + + public void updateCaption(ComponentConnector child) { + Widget childWidget = child.getWidget(); + FlowPane cssLayoutWidgetContainer = getWidget().panel; + int widgetPosition = cssLayoutWidgetContainer + .getWidgetIndex(childWidget); + + VCaption caption = childToCaption.get(child); + if (VCaption.isNeeded(child.getState())) { + if (caption == null) { + caption = new VCaption(child, getConnection()); + childToCaption.put(child, caption); + } + if (!caption.isAttached()) { + // Insert caption at widget index == before widget + cssLayoutWidgetContainer.insert(caption, widgetPosition); + } + caption.updateCaption(); + } else if (caption != null) { + childToCaption.remove(child); + cssLayoutWidgetContainer.remove(caption); + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutServerRpc.java new file mode 100644 index 0000000000..7ba89d4c4c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutServerRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.csslayout; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; + +public interface CssLayoutServerRpc extends LayoutClickRpc, ServerRpc { + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutState.java b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutState.java new file mode 100644 index 0000000000..07a8c1804a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutState.java @@ -0,0 +1,23 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.csslayout; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutState; + +public class CssLayoutState extends AbstractLayoutState { + private Map<Connector, String> childCss = new HashMap<Connector, String>(); + + public Map<Connector, String> getChildCss() { + return childCss; + } + + public void setChildCss(Map<Connector, String> childCss) { + this.childCss = childCss; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/csslayout/VCssLayout.java b/src/com/vaadin/terminal/gwt/client/ui/csslayout/VCssLayout.java new file mode 100644 index 0000000000..7076120388 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/csslayout/VCssLayout.java @@ -0,0 +1,72 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.csslayout; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.StyleConstants; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; + +public class VCssLayout extends SimplePanel { + public static final String TAGNAME = "csslayout"; + public static final String CLASSNAME = "v-" + TAGNAME; + + FlowPane panel = new FlowPane(); + + Element margin = DOM.createDiv(); + + public VCssLayout() { + super(); + getElement().appendChild(margin); + setStyleName(CLASSNAME); + margin.setClassName(CLASSNAME + "-margin"); + setWidget(panel); + } + + @Override + protected Element getContainerElement() { + return margin; + } + + public class FlowPane extends FlowPanel { + + public FlowPane() { + super(); + setStyleName(CLASSNAME + "-container"); + } + + void addOrMove(Widget child, int index) { + if (child.getParent() == this) { + int currentIndex = getWidgetIndex(child); + if (index == currentIndex) { + return; + } + } + insert(child, index); + } + + } + + /** + * Sets CSS classes for margin based on the given parameters. + * + * @param margins + * A {@link VMarginInfo} object that provides info on + * top/left/bottom/right margins + */ + protected void setMarginStyles(VMarginInfo margins) { + setStyleName(margin, VCssLayout.CLASSNAME + "-" + + StyleConstants.MARGIN_TOP, margins.hasTop()); + setStyleName(margin, VCssLayout.CLASSNAME + "-" + + StyleConstants.MARGIN_RIGHT, margins.hasRight()); + setStyleName(margin, VCssLayout.CLASSNAME + "-" + + StyleConstants.MARGIN_BOTTOM, margins.hasBottom()); + setStyleName(margin, VCssLayout.CLASSNAME + "-" + + StyleConstants.MARGIN_LEFT, margins.hasLeft()); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/customcomponent/CustomComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/customcomponent/CustomComponentConnector.java new file mode 100644 index 0000000000..a65187a461 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/customcomponent/CustomComponentConnector.java @@ -0,0 +1,46 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.customcomponent; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.CustomComponent; + +@Connect(value = CustomComponent.class, loadStyle = LoadStyle.EAGER) +public class CustomComponentConnector extends + AbstractComponentContainerConnector { + + @Override + protected Widget createWidget() { + return GWT.create(VCustomComponent.class); + } + + @Override + public VCustomComponent getWidget() { + return (VCustomComponent) super.getWidget(); + } + + public void updateCaption(ComponentConnector component) { + // NOP, custom component dont render composition roots caption + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + ComponentConnector newChild = null; + if (getChildren().size() == 1) { + newChild = getChildren().get(0); + } + + VCustomComponent customComponent = getWidget(); + customComponent.setWidget(newChild.getWidget()); + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/customcomponent/VCustomComponent.java b/src/com/vaadin/terminal/gwt/client/ui/customcomponent/VCustomComponent.java new file mode 100644 index 0000000000..2b27bd0e58 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/customcomponent/VCustomComponent.java @@ -0,0 +1,18 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.customcomponent; + +import com.google.gwt.user.client.ui.SimplePanel; + +public class VCustomComponent extends SimplePanel { + + private static final String CLASSNAME = "v-customcomponent"; + + public VCustomComponent() { + super(); + setStyleName(CLASSNAME); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/customfield/CustomFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/customfield/CustomFieldConnector.java new file mode 100644 index 0000000000..a2ba09da43 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/customfield/CustomFieldConnector.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.customfield; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.customcomponent.CustomComponentConnector; +import com.vaadin.terminal.gwt.client.ui.customcomponent.VCustomComponent; +import com.vaadin.ui.CustomField; + +@Connect(value = CustomField.class) +public class CustomFieldConnector extends CustomComponentConnector { + + @Override + protected Widget createWidget() { + return GWT.create(VCustomComponent.class); + } + + @Override + public VCustomComponent getWidget() { + return super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutConnector.java new file mode 100644 index 0000000000..5e8f01258f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutConnector.java @@ -0,0 +1,118 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.customlayout; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.ui.CustomLayout; + +@Connect(CustomLayout.class) +public class CustomLayoutConnector extends AbstractLayoutConnector implements + SimpleManagedLayout { + + @Override + public CustomLayoutState getState() { + return (CustomLayoutState) super.getState(); + } + + @Override + protected void init() { + super.init(); + getWidget().client = getConnection(); + getWidget().pid = getConnectorId(); + + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + // Evaluate scripts + VCustomLayout.eval(getWidget().scripts); + getWidget().scripts = null; + + } + + private void updateHtmlTemplate() { + if (getWidget().hasTemplate()) { + // We (currently) only do this once. You can't change the template + // later on. + return; + } + String templateName = getState().getTemplateName(); + String templateContents = getState().getTemplateContents(); + + if (templateName != null) { + // Get the HTML-template from client. Overrides templateContents + // (even though both can never be given at the same time) + templateContents = getConnection().getResource( + "layouts/" + templateName + ".html"); + if (templateContents == null) { + templateContents = "<em>Layout file layouts/" + + templateName + + ".html is missing. Components will be drawn for debug purposes.</em>"; + } + } + + getWidget().initializeHTML(templateContents, + getConnection().getThemeUri()); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + // Must do this once here so the HTML has been set up before we start + // adding child widgets. + + updateHtmlTemplate(); + + // For all contained widgets + for (ComponentConnector child : getChildren()) { + String location = getState().getChildLocations().get(child); + try { + getWidget().setWidget(child.getWidget(), location); + } catch (final IllegalArgumentException e) { + // If no location is found, this component is not visible + } + } + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() == this) { + // Connector still a child of this + continue; + } + Widget oldChildWidget = oldChild.getWidget(); + if (oldChildWidget.isAttached()) { + // slot of this widget is emptied, remove it + getWidget().remove(oldChildWidget); + } + } + + } + + @Override + public VCustomLayout getWidget() { + return (VCustomLayout) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VCustomLayout.class); + } + + public void updateCaption(ComponentConnector paintable) { + getWidget().updateCaption(paintable); + } + + public void layout() { + getWidget().iLayoutJS(DOM.getFirstChild(getWidget().getElement())); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutState.java b/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutState.java new file mode 100644 index 0000000000..6b374a8099 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutState.java @@ -0,0 +1,41 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.customlayout; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutState; + +public class CustomLayoutState extends AbstractLayoutState { + Map<Connector, String> childLocations = new HashMap<Connector, String>(); + private String templateContents; + private String templateName; + + public String getTemplateContents() { + return templateContents; + } + + public void setTemplateContents(String templateContents) { + this.templateContents = templateContents; + } + + public String getTemplateName() { + return templateName; + } + + public void setTemplateName(String templateName) { + this.templateName = templateName; + } + + public Map<Connector, String> getChildLocations() { + return childLocations; + } + + public void setChildLocations(Map<Connector, String> childLocations) { + this.childLocations = childLocations; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java b/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java index f4aacf3ea2..b4194c40a6 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java @@ -2,12 +2,10 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.customlayout; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; -import java.util.Set; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.NodeList; @@ -18,12 +16,7 @@ import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.ContainerResizedListener; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VCaption; import com.vaadin.terminal.gwt.client.VCaptionWrapper; @@ -34,8 +27,7 @@ import com.vaadin.terminal.gwt.client.VCaptionWrapper; * @author Vaadin Ltd * */ -public class VCustomLayout extends ComplexPanel implements Paintable, - Container, ContainerResizedListener { +public class VCustomLayout extends ComplexPanel { public static final String CLASSNAME = "v-customlayout"; @@ -43,24 +35,23 @@ public class VCustomLayout extends ComplexPanel implements Paintable, private final HashMap<String, Element> locationToElement = new HashMap<String, Element>(); /** Location-name to contained widget map */ - private final HashMap<String, Widget> locationToWidget = new HashMap<String, Widget>(); + final HashMap<String, Widget> locationToWidget = new HashMap<String, Widget>(); /** Widget to captionwrapper map */ - private final HashMap<Paintable, VCaptionWrapper> widgetToCaptionWrapper = new HashMap<Paintable, VCaptionWrapper>(); + private final HashMap<Widget, VCaptionWrapper> childWidgetToCaptionWrapper = new HashMap<Widget, VCaptionWrapper>(); /** Name of the currently rendered style */ String currentTemplateName; /** Unexecuted scripts loaded from the template */ - private String scripts = ""; + String scripts = ""; /** Paintable ID of this paintable */ - private String pid; + String pid; - private ApplicationConnection client; + ApplicationConnection client; - /** Has the template been loaded from contents passed in UIDL **/ - private boolean hasTemplateContents = false; + private boolean htmlInitialized = false; private Element elementWithNativeResizeFunction; @@ -68,8 +59,6 @@ public class VCustomLayout extends ComplexPanel implements Paintable, private String width = ""; - private HashMap<String, FloatSize> locationToExtraSize = new HashMap<String, FloatSize>(); - public VCustomLayout() { setElement(DOM.createDiv()); // Clear any unwanted styling @@ -131,94 +120,14 @@ public class VCustomLayout extends ComplexPanel implements Paintable, locationToWidget.put(location, widget); } - /** Update the layout from UIDL */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - // ApplicationConnection manages generic component features - if (client.updateComponent(this, uidl, true)) { - return; - } - - pid = uidl.getId(); - if (!hasTemplate()) { - // Update HTML template only once - initializeHTML(uidl, client); - } - - // Evaluate scripts - eval(scripts); - scripts = null; - - iLayout(); - // TODO Check if this is needed - client.runDescendentsLayout(this); - - Set<Widget> oldWidgets = new HashSet<Widget>(); - oldWidgets.addAll(locationToWidget.values()); - - // For all contained widgets - for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) { - final UIDL uidlForChild = (UIDL) i.next(); - if (uidlForChild.getTag().equals("location")) { - final String location = uidlForChild.getStringAttribute("name"); - final Paintable child = client.getPaintable(uidlForChild - .getChildUIDL(0)); - try { - setWidget((Widget) child, location); - child.updateFromUIDL(uidlForChild.getChildUIDL(0), client); - } catch (final IllegalArgumentException e) { - // If no location is found, this component is not visible - } - oldWidgets.remove(child); - } - } - for (Iterator<Widget> iterator = oldWidgets.iterator(); iterator - .hasNext();) { - Widget oldWidget = iterator.next(); - if (oldWidget.isAttached()) { - // slot of this widget is emptied, remove it - remove(oldWidget); - } - } - - iLayout(); - // TODO Check if this is needed - client.runDescendentsLayout(this); - - } - /** Initialize HTML-layout. */ - private void initializeHTML(UIDL uidl, ApplicationConnection client) { - - final String newTemplateContents = uidl - .getStringAttribute("templateContents"); - final String newTemplate = uidl.getStringAttribute("template"); - - currentTemplateName = null; - hasTemplateContents = false; - - String template = ""; - if (newTemplate != null) { - // Get the HTML-template from client - template = client.getResource("layouts/" + newTemplate + ".html"); - if (template == null) { - template = "<em>Layout file layouts/" - + newTemplate - + ".html is missing. Components will be drawn for debug purposes.</em>"; - } else { - currentTemplateName = newTemplate; - } - } else { - hasTemplateContents = true; - template = newTemplateContents; - } + public void initializeHTML(String template, String themeUri) { // Connect body of the template to DOM template = extractBodyAndScriptsFromTemplate(template); // TODO prefix img src:s here with a regeps, cannot work further with IE - String themeUri = client.getThemeUri(); String relImgPrefix = themeUri + "/layouts/"; // prefix all relative image elements to point to theme dir with a @@ -251,6 +160,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, } publishResizedFunction(elementWithNativeResizeFunction); + htmlInitialized = true; } private native boolean uriEndsWithSlash() @@ -261,12 +171,8 @@ public class VCustomLayout extends ComplexPanel implements Paintable, return false; }-*/; - private boolean hasTemplate() { - if (currentTemplateName == null && !hasTemplateContents) { - return false; - } else { - return true; - } + boolean hasTemplate() { + return htmlInitialized; } /** Collect locations from template */ @@ -276,12 +182,6 @@ public class VCustomLayout extends ComplexPanel implements Paintable, if (!"".equals(location)) { locationToElement.put(location, elem); elem.setInnerHTML(""); - int x = Util.measureHorizontalPaddingAndBorder(elem, 0); - int y = Util.measureVerticalPaddingAndBorder(elem, 0); - - FloatSize fs = new FloatSize(x, y); - - locationToExtraSize.put(location, fs); } else { final int len = DOM.getChildCount(elem); @@ -292,7 +192,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, } /** Evaluate given script in browser document */ - private static native void eval(String script) + static native void eval(String script) /*-{ try { if (script != null) @@ -369,39 +269,27 @@ public class VCustomLayout extends ComplexPanel implements Paintable, return res; } - /** Replace child components */ - public void replaceChildComponent(Widget from, Widget to) { - final String location = getLocation(from); - if (location == null) { - throw new IllegalArgumentException(); - } - setWidget(to, location); - } - - /** Does this layout contain given child */ - public boolean hasChildComponent(Widget component) { - return locationToWidget.containsValue(component); - } - /** Update caption for given widget */ - public void updateCaption(Paintable component, UIDL uidl) { - VCaptionWrapper wrapper = widgetToCaptionWrapper.get(component); - if (VCaption.isNeeded(uidl)) { + public void updateCaption(ComponentConnector paintable) { + Widget widget = paintable.getWidget(); + VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget); + if (VCaption.isNeeded(paintable.getState())) { if (wrapper == null) { - final String loc = getLocation((Widget) component); - super.remove((Widget) component); - wrapper = new VCaptionWrapper(component, client); + // Add a wrapper between the layout and the child widget + final String loc = getLocation(widget); + super.remove(widget); + wrapper = new VCaptionWrapper(paintable, client); super.add(wrapper, locationToElement.get(loc)); - widgetToCaptionWrapper.put(component, wrapper); + childWidgetToCaptionWrapper.put(widget, wrapper); } - wrapper.updateCaption(uidl); + wrapper.updateCaption(); } else { if (wrapper != null) { - final String loc = getLocation((Widget) component); + // Remove the wrapper and add the widget directly to the layout + final String loc = getLocation(widget); super.remove(wrapper); - super.add((Widget) wrapper.getPaintable(), - locationToElement.get(loc)); - widgetToCaptionWrapper.remove(component); + super.add(widget, locationToElement.get(loc)); + childWidgetToCaptionWrapper.remove(widget); } } } @@ -421,14 +309,13 @@ public class VCustomLayout extends ComplexPanel implements Paintable, /** Removes given widget from the layout */ @Override public boolean remove(Widget w) { - client.unregisterPaintable((Paintable) w); final String location = getLocation(w); if (location != null) { locationToWidget.remove(location); } - final VCaptionWrapper cw = widgetToCaptionWrapper.get(w); + final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w); if (cw != null) { - widgetToCaptionWrapper.remove(w); + childWidgetToCaptionWrapper.remove(w); return super.remove(cw); } else if (w != null) { return super.remove(w); @@ -447,11 +334,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, public void clear() { super.clear(); locationToWidget.clear(); - widgetToCaptionWrapper.clear(); - } - - public void iLayout() { - iLayoutJS(DOM.getFirstChild(getElement())); + childWidgetToCaptionWrapper.clear(); } /** @@ -480,7 +363,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, /*-{ var self = this; element.notifyChildrenOfSizeChange = $entry(function() { - self.@com.vaadin.terminal.gwt.client.ui.VCustomLayout::notifyChildrenOfSizeChange()(); + self.@com.vaadin.terminal.gwt.client.ui.customlayout.VCustomLayout::notifyChildrenOfSizeChange()(); }); }-*/; @@ -499,7 +382,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, * @return true if layout function exists and was run successfully, else * false. */ - private native boolean iLayoutJS(Element el) + native boolean iLayoutJS(Element el) /*-{ if(el && el.iLayoutJS) { try { @@ -513,27 +396,6 @@ public class VCustomLayout extends ComplexPanel implements Paintable, } }-*/; - public boolean requestLayout(Set<Paintable> child) { - updateRelativeSizedComponents(true, true); - - if (width.equals("") || height.equals("")) { - /* Automatically propagated upwards if the size can change */ - return false; - } - - return true; - } - - public RenderSpace getAllocatedSpace(Widget child) { - com.google.gwt.dom.client.Element pe = child.getElement() - .getParentElement(); - - FloatSize extra = locationToExtraSize.get(getLocation(child)); - return new RenderSpace(pe.getOffsetWidth() - (int) extra.getWidth(), - pe.getOffsetHeight() - (int) extra.getHeight(), - Util.mayHaveScrollBars(pe)); - } - @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -543,103 +405,4 @@ public class VCustomLayout extends ComplexPanel implements Paintable, } } - @Override - public void setHeight(String height) { - if (this.height.equals(height)) { - return; - } - - boolean shrinking = true; - if (isLarger(height, this.height)) { - shrinking = false; - } - - this.height = height; - super.setHeight(height); - - /* - * If the height shrinks we must remove all components with relative - * height from the DOM, update their height when they do not affect the - * available space and finally restore them to the original state - */ - if (shrinking) { - updateRelativeSizedComponents(false, true); - } - } - - @Override - public void setWidth(String width) { - if (this.width.equals(width)) { - return; - } - - boolean shrinking = true; - if (isLarger(width, this.width)) { - shrinking = false; - } - - super.setWidth(width); - this.width = width; - - /* - * If the width shrinks we must remove all components with relative - * width from the DOM, update their width when they do not affect the - * available space and finally restore them to the original state - */ - if (shrinking) { - updateRelativeSizedComponents(true, false); - } - } - - private void updateRelativeSizedComponents(boolean relativeWidth, - boolean relativeHeight) { - - Set<Widget> relativeSizeWidgets = new HashSet<Widget>(); - - for (Widget widget : locationToWidget.values()) { - FloatSize relativeSize = client.getRelativeSize(widget); - if (relativeSize != null) { - if ((relativeWidth && (relativeSize.getWidth() >= 0.0f)) - || (relativeHeight && (relativeSize.getHeight() >= 0.0f))) { - - relativeSizeWidgets.add(widget); - widget.getElement().getStyle() - .setProperty("position", "absolute"); - } - } - } - - for (Widget widget : relativeSizeWidgets) { - client.handleComponentRelativeSize(widget); - widget.getElement().getStyle().setProperty("position", ""); - } - } - - /** - * Compares newSize with currentSize and returns true if it is clear that - * newSize is larger than currentSize. Returns false if newSize is smaller - * or if it is unclear which one is smaller. - * - * @param newSize - * @param currentSize - * @return - */ - private boolean isLarger(String newSize, String currentSize) { - if (newSize.equals("") || currentSize.equals("")) { - return false; - } - - if (!newSize.endsWith("px") || !currentSize.endsWith("px")) { - return false; - } - - int newSizePx = Integer.parseInt(newSize.substring(0, - newSize.length() - 2)); - int currentSizePx = Integer.parseInt(currentSize.substring(0, - currentSize.length() - 2)); - - boolean larger = newSizePx > currentSizePx; - return larger; - } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/datefield/AbstractDateFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/AbstractDateFieldConnector.java new file mode 100644 index 0000000000..e19d9b996f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/AbstractDateFieldConnector.java @@ -0,0 +1,109 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.datefield; + +import java.util.Date; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.LocaleNotLoadedException; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; + +public class AbstractDateFieldConnector extends AbstractFieldConnector + implements Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + getWidget().immediate = getState().isImmediate(); + + getWidget().readonly = isReadOnly(); + getWidget().enabled = isEnabled(); + + if (uidl.hasAttribute("locale")) { + final String locale = uidl.getStringAttribute("locale"); + try { + getWidget().dts.setLocale(locale); + getWidget().currentLocale = locale; + } catch (final LocaleNotLoadedException e) { + getWidget().currentLocale = getWidget().dts.getLocale(); + VConsole.error("Tried to use an unloaded locale \"" + locale + + "\". Using default locale (" + + getWidget().currentLocale + ")."); + VConsole.error(e); + } + } + + // We show week numbers only if the week starts with Monday, as ISO 8601 + // specifies + getWidget().showISOWeekNumbers = uidl + .getBooleanAttribute(VDateField.WEEK_NUMBERS) + && getWidget().dts.getFirstDayOfWeek() == 1; + + int newResolution; + if (uidl.hasVariable("sec")) { + newResolution = VDateField.RESOLUTION_SEC; + } else if (uidl.hasVariable("min")) { + newResolution = VDateField.RESOLUTION_MIN; + } else if (uidl.hasVariable("hour")) { + newResolution = VDateField.RESOLUTION_HOUR; + } else if (uidl.hasVariable("day")) { + newResolution = VDateField.RESOLUTION_DAY; + } else if (uidl.hasVariable("month")) { + newResolution = VDateField.RESOLUTION_MONTH; + } else { + newResolution = VDateField.RESOLUTION_YEAR; + } + + getWidget().currentResolution = newResolution; + + // Add stylename that indicates current resolution + getWidget() + .addStyleName( + VDateField.CLASSNAME + + "-" + + VDateField + .resolutionToString(getWidget().currentResolution)); + + final int year = uidl.getIntVariable("year"); + final int month = (getWidget().currentResolution >= VDateField.RESOLUTION_MONTH) ? uidl + .getIntVariable("month") : -1; + final int day = (getWidget().currentResolution >= VDateField.RESOLUTION_DAY) ? uidl + .getIntVariable("day") : -1; + final int hour = (getWidget().currentResolution >= VDateField.RESOLUTION_HOUR) ? uidl + .getIntVariable("hour") : 0; + final int min = (getWidget().currentResolution >= VDateField.RESOLUTION_MIN) ? uidl + .getIntVariable("min") : 0; + final int sec = (getWidget().currentResolution >= VDateField.RESOLUTION_SEC) ? uidl + .getIntVariable("sec") : 0; + + // Construct new date for this datefield (only if not null) + if (year > -1) { + getWidget().setCurrentDate( + new Date((long) getWidget().getTime(year, month, day, hour, + min, sec, 0))); + } else { + getWidget().setCurrentDate(null); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VDateField.class); + } + + @Override + public VDateField getWidget() { + return (VDateField) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/datefield/InlineDateFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/InlineDateFieldConnector.java new file mode 100644 index 0000000000..8a4840a088 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/InlineDateFieldConnector.java @@ -0,0 +1,104 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.datefield; + +import java.util.Date; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.DateTimeService; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.FocusChangeListener; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.TimeChangeListener; +import com.vaadin.ui.InlineDateField; + +@Connect(InlineDateField.class) +public class InlineDateFieldConnector extends AbstractDateFieldConnector { + + @Override + @SuppressWarnings("deprecation") + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().calendarPanel.setShowISOWeekNumbers(getWidget() + .isShowISOWeekNumbers()); + getWidget().calendarPanel.setDateTimeService(getWidget() + .getDateTimeService()); + getWidget().calendarPanel.setResolution(getWidget() + .getCurrentResolution()); + Date currentDate = getWidget().getCurrentDate(); + if (currentDate != null) { + getWidget().calendarPanel.setDate(new Date(currentDate.getTime())); + } else { + getWidget().calendarPanel.setDate(null); + } + + if (getWidget().currentResolution > VDateField.RESOLUTION_DAY) { + getWidget().calendarPanel + .setTimeChangeListener(new TimeChangeListener() { + public void changed(int hour, int min, int sec, int msec) { + Date d = getWidget().getDate(); + if (d == null) { + // date currently null, use the value from + // calendarPanel + // (~ client time at the init of the widget) + d = (Date) getWidget().calendarPanel.getDate() + .clone(); + } + d.setHours(hour); + d.setMinutes(min); + d.setSeconds(sec); + DateTimeService.setMilliseconds(d, msec); + + // Always update time changes to the server + getWidget().calendarPanel.setDate(d); + getWidget().updateValueFromPanel(); + } + }); + } + + if (getWidget().currentResolution <= VDateField.RESOLUTION_MONTH) { + getWidget().calendarPanel + .setFocusChangeListener(new FocusChangeListener() { + public void focusChanged(Date date) { + Date date2 = new Date(); + if (getWidget().calendarPanel.getDate() != null) { + date2.setTime(getWidget().calendarPanel + .getDate().getTime()); + } + /* + * Update the value of calendarPanel + */ + date2.setYear(date.getYear()); + date2.setMonth(date.getMonth()); + getWidget().calendarPanel.setDate(date2); + /* + * Then update the value from panel to server + */ + getWidget().updateValueFromPanel(); + } + }); + } else { + getWidget().calendarPanel.setFocusChangeListener(null); + } + + // Update possible changes + getWidget().calendarPanel.renderCalendar(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VDateFieldCalendar.class); + } + + @Override + public VDateFieldCalendar getWidget() { + return (VDateFieldCalendar) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/datefield/PopupDateFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/PopupDateFieldConnector.java new file mode 100644 index 0000000000..1bcb40d549 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/PopupDateFieldConnector.java @@ -0,0 +1,124 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.datefield; + +import java.util.Date; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.DateTimeService; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.FocusChangeListener; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.TimeChangeListener; +import com.vaadin.ui.DateField; + +@Connect(DateField.class) +public class PopupDateFieldConnector extends TextualDateConnector { + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ui.VTextualDate#updateFromUIDL(com.vaadin + * .terminal.gwt.client.UIDL, + * com.vaadin.terminal.gwt.client.ApplicationConnection) + */ + @Override + @SuppressWarnings("deprecation") + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + boolean lastReadOnlyState = getWidget().readonly; + boolean lastEnabledState = getWidget().isEnabled(); + + getWidget().parsable = uidl.getBooleanAttribute("parsable"); + + super.updateFromUIDL(uidl, client); + + String popupStyleNames = getStyleNames(VPopupCalendar.POPUP_PRIMARY_STYLE_NAME); + popupStyleNames += " " + + VDateField.CLASSNAME + + "-" + + VPopupCalendar + .resolutionToString(getWidget().currentResolution); + getWidget().popup.setStyleName(popupStyleNames); + + getWidget().calendar.setDateTimeService(getWidget() + .getDateTimeService()); + getWidget().calendar.setShowISOWeekNumbers(getWidget() + .isShowISOWeekNumbers()); + if (getWidget().calendar.getResolution() != getWidget().currentResolution) { + getWidget().calendar.setResolution(getWidget().currentResolution); + if (getWidget().calendar.getDate() != null) { + getWidget().calendar.setDate((Date) getWidget() + .getCurrentDate().clone()); + // force re-render when changing resolution only + getWidget().calendar.renderCalendar(); + } + } + getWidget().calendarToggle.setEnabled(getWidget().enabled); + + if (getWidget().currentResolution <= VPopupCalendar.RESOLUTION_MONTH) { + getWidget().calendar + .setFocusChangeListener(new FocusChangeListener() { + public void focusChanged(Date date) { + getWidget().updateValue(date); + getWidget().buildDate(); + Date date2 = getWidget().calendar.getDate(); + date2.setYear(date.getYear()); + date2.setMonth(date.getMonth()); + } + }); + } else { + getWidget().calendar.setFocusChangeListener(null); + } + + if (getWidget().currentResolution > VPopupCalendar.RESOLUTION_DAY) { + getWidget().calendar + .setTimeChangeListener(new TimeChangeListener() { + public void changed(int hour, int min, int sec, int msec) { + Date d = getWidget().getDate(); + if (d == null) { + // date currently null, use the value from + // calendarPanel + // (~ client time at the init of the widget) + d = (Date) getWidget().calendar.getDate() + .clone(); + } + d.setHours(hour); + d.setMinutes(min); + d.setSeconds(sec); + DateTimeService.setMilliseconds(d, msec); + + // Always update time changes to the server + getWidget().updateValue(d); + + // Update text field + getWidget().buildDate(); + } + }); + } + + if (getWidget().readonly) { + getWidget().calendarToggle.addStyleName(VPopupCalendar.CLASSNAME + + "-button-readonly"); + } else { + getWidget().calendarToggle.removeStyleName(VPopupCalendar.CLASSNAME + + "-button-readonly"); + } + + getWidget().calendarToggle.setEnabled(true); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPopupCalendar.class); + } + + @Override + public VPopupCalendar getWidget() { + return (VPopupCalendar) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/datefield/TextualDateConnector.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/TextualDateConnector.java new file mode 100644 index 0000000000..90679f5705 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/TextualDateConnector.java @@ -0,0 +1,56 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.datefield; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; + +public class TextualDateConnector extends AbstractDateFieldConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + int origRes = getWidget().currentResolution; + String oldLocale = getWidget().currentLocale; + super.updateFromUIDL(uidl, client); + if (origRes != getWidget().currentResolution + || oldLocale != getWidget().currentLocale) { + // force recreating format string + getWidget().formatStr = null; + } + if (uidl.hasAttribute("format")) { + getWidget().formatStr = uidl.getStringAttribute("format"); + } + + getWidget().inputPrompt = uidl + .getStringAttribute(VTextualDate.ATTR_INPUTPROMPT); + + getWidget().lenient = !uidl.getBooleanAttribute("strict"); + + getWidget().buildDate(); + // not a FocusWidget -> needs own tabindex handling + if (uidl.hasAttribute("tabindex")) { + getWidget().text.setTabIndex(uidl.getIntAttribute("tabindex")); + } + + if (getWidget().readonly) { + getWidget().text.addStyleDependentName("readonly"); + } else { + getWidget().text.removeStyleDependentName("readonly"); + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextualDate.class); + } + + @Override + public VTextualDate getWidget() { + return (VTextualDate) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/VCalendarPanel.java index 845ac837f6..acfff60d53 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/VCalendarPanel.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.datefield; import java.util.Date; import java.util.Iterator; @@ -40,6 +40,10 @@ import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.DateTimeService; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.FocusableFlexTable; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.label.VLabel; +import com.vaadin.terminal.gwt.client.ui.nativeselect.VNativeSelect; @SuppressWarnings("deprecation") public class VCalendarPanel extends FocusableFlexTable implements @@ -1139,7 +1143,7 @@ public class VCalendarPanel extends FocusableFlexTable implements // elapsed, another timer is triggered to go off every 150ms. Both // timers are cancelled on mouseup or mouseout. if (event.getSource() instanceof VEventButton) { - final Widget sender = (Widget) event.getSource(); + final VEventButton sender = (VEventButton) event.getSource(); processClickEvent(sender); mouseTimer = new Timer() { @Override @@ -1228,8 +1232,6 @@ public class VCalendarPanel extends FocusableFlexTable implements private ListBox sec; - private ListBox msec; - private ListBox ampm; /** @@ -1294,19 +1296,6 @@ public class VCalendarPanel extends FocusableFlexTable implements } sec.addChangeHandler(this); } - if (getResolution() == VDateField.RESOLUTION_MSEC) { - msec = createListBox(); - for (int i = 0; i < 1000; i++) { - if (i < 10) { - msec.addItem("00" + i); - } else if (i < 100) { - msec.addItem("0" + i); - } else { - msec.addItem("" + i); - } - } - msec.addChangeHandler(this); - } final String delimiter = getDateTimeService().getClockDelimeter(); if (isReadonly()) { @@ -1340,16 +1329,6 @@ public class VCalendarPanel extends FocusableFlexTable implements add(sec); } } - if (getResolution() == VDateField.RESOLUTION_MSEC) { - add(new VLabel(".")); - if (isReadonly()) { - final int m = getMilliseconds(); - final String ms = m < 100 ? "0" + m : "" + m; - add(new VLabel(m < 10 ? "0" + ms : ms)); - } else { - add(msec); - } - } if (getResolution() == VDateField.RESOLUTION_HOUR) { add(new VLabel(delimiter + "00")); // o'clock } @@ -1425,13 +1404,6 @@ public class VCalendarPanel extends FocusableFlexTable implements if (getResolution() >= VDateField.RESOLUTION_SEC) { sec.setSelectedIndex(value.getSeconds()); } - if (getResolution() == VDateField.RESOLUTION_MSEC) { - if (selected) { - msec.setSelectedIndex(getMilliseconds()); - } else { - msec.setSelectedIndex(0); - } - } if (getDateTimeService().isTwelveHourClock()) { ampm.setSelectedIndex(value.getHours() < 12 ? 0 : 1); } @@ -1443,9 +1415,6 @@ public class VCalendarPanel extends FocusableFlexTable implements if (sec != null) { sec.setEnabled(isEnabled()); } - if (msec != null) { - msec.setEnabled(isEnabled()); - } if (ampm != null) { ampm.setEnabled(isEnabled()); } @@ -1508,15 +1477,6 @@ public class VCalendarPanel extends FocusableFlexTable implements } event.preventDefault(); event.stopPropagation(); - } else if (event.getSource() == msec) { - final int ms = msec.getSelectedIndex(); - DateTimeService.setMilliseconds(value, ms); - if (timeChangeListener != null) { - timeChangeListener.changed(value.getHours(), - value.getMinutes(), value.getSeconds(), ms); - } - event.preventDefault(); - event.stopPropagation(); } else if (event.getSource() == ampm) { final int h = hours.getSelectedIndex() + (ampm.getSelectedIndex() * 12); @@ -1699,8 +1659,6 @@ public class VCalendarPanel extends FocusableFlexTable implements return SUBPART_MINUTE_SELECT; } else if (contains(time.sec, subElement)) { return SUBPART_SECS_SELECT; - } else if (contains(time.msec, subElement)) { - return SUBPART_MSECS_SELECT; } else if (contains(time.ampm, subElement)) { return SUBPART_AMPM_SELECT; @@ -1749,9 +1707,6 @@ public class VCalendarPanel extends FocusableFlexTable implements if (SUBPART_SECS_SELECT.equals(subPart)) { return time.sec.getElement(); } - if (SUBPART_MSECS_SELECT.equals(subPart)) { - return time.msec.getElement(); - } if (SUBPART_AMPM_SELECT.equals(subPart)) { return time.ampm.getElement(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDateField.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateField.java index bc228675b2..d169b1b47e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDateField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateField.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.datefield; import java.util.Date; @@ -10,19 +10,16 @@ import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.FlowPanel; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.LocaleNotLoadedException; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Field; -public class VDateField extends FlowPanel implements Paintable, Field { +public class VDateField extends FlowPanel implements Field { public static final String CLASSNAME = "v-datefield"; - private String id; + protected String paintableId; - private ApplicationConnection client; + protected ApplicationConnection client; protected boolean immediate; @@ -32,7 +29,6 @@ public class VDateField extends FlowPanel implements Paintable, Field { public static final int RESOLUTION_HOUR = 8; public static final int RESOLUTION_MIN = 16; public static final int RESOLUTION_SEC = 32; - public static final int RESOLUTION_MSEC = 64; public static final String WEEK_NUMBERS = "wn"; @@ -65,7 +61,7 @@ public class VDateField extends FlowPanel implements Paintable, Field { protected DateTimeService dts; - private boolean showISOWeekNumbers = false; + protected boolean showISOWeekNumbers = false; public VDateField() { setStyleName(CLASSNAME); @@ -81,88 +77,11 @@ public class VDateField extends FlowPanel implements Paintable, Field { } } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Ensure correct implementation and let layout manage caption - if (client.updateComponent(this, uidl, true)) { - return; - } - - // Save details - this.client = client; - id = uidl.getId(); - immediate = uidl.getBooleanAttribute("immediate"); - - readonly = uidl.getBooleanAttribute("readonly"); - enabled = !uidl.getBooleanAttribute("disabled"); - - if (uidl.hasAttribute("locale")) { - final String locale = uidl.getStringAttribute("locale"); - try { - dts.setLocale(locale); - currentLocale = locale; - } catch (final LocaleNotLoadedException e) { - currentLocale = dts.getLocale(); - VConsole.error("Tried to use an unloaded locale \"" + locale - + "\". Using default locale (" + currentLocale + ")."); - VConsole.error(e); - } - } - - // We show week numbers only if the week starts with Monday, as ISO 8601 - // specifies - showISOWeekNumbers = uidl.getBooleanAttribute(WEEK_NUMBERS) - && dts.getFirstDayOfWeek() == 1; - - int newResolution; - if (uidl.hasVariable("msec")) { - newResolution = RESOLUTION_MSEC; - } else if (uidl.hasVariable("sec")) { - newResolution = RESOLUTION_SEC; - } else if (uidl.hasVariable("min")) { - newResolution = RESOLUTION_MIN; - } else if (uidl.hasVariable("hour")) { - newResolution = RESOLUTION_HOUR; - } else if (uidl.hasVariable("day")) { - newResolution = RESOLUTION_DAY; - } else if (uidl.hasVariable("month")) { - newResolution = RESOLUTION_MONTH; - } else { - newResolution = RESOLUTION_YEAR; - } - - currentResolution = newResolution; - - // Add stylename that indicates current resolution - addStyleName(CLASSNAME + "-" + resolutionToString(currentResolution)); - - final int year = uidl.getIntVariable("year"); - final int month = (currentResolution >= RESOLUTION_MONTH) ? uidl - .getIntVariable("month") : -1; - final int day = (currentResolution >= RESOLUTION_DAY) ? uidl - .getIntVariable("day") : -1; - final int hour = (currentResolution >= RESOLUTION_HOUR) ? uidl - .getIntVariable("hour") : 0; - final int min = (currentResolution >= RESOLUTION_MIN) ? uidl - .getIntVariable("min") : 0; - final int sec = (currentResolution >= RESOLUTION_SEC) ? uidl - .getIntVariable("sec") : 0; - final int msec = (currentResolution >= RESOLUTION_MSEC) ? uidl - .getIntVariable("msec") : 0; - - // Construct new date for this datefield (only if not null) - if (year > -1) { - setCurrentDate(new Date((long) getTime(year, month, day, hour, min, - sec, msec))); - } else { - setCurrentDate(null); - } - } - /* * We need this redundant native function because Java's Date object doesn't * have a setMilliseconds method. */ - private static native double getTime(int y, int m, int d, int h, int mi, + protected static native double getTime(int y, int m, int d, int h, int mi, int s, int ms) /*-{ try { @@ -231,7 +150,7 @@ public class VDateField extends FlowPanel implements Paintable, Field { } public String getId() { - return id; + return paintableId; } public ApplicationConnection getClient() { diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateFieldCalendar.java index 91388edcaf..21e0e0820d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateFieldCalendar.java @@ -2,25 +2,21 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.datefield; import java.util.Date; import com.google.gwt.event.dom.client.DomEvent; -import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusChangeListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.SubmitListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.FocusOutListener; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.SubmitListener; /** * A client side implementation for InlineDateField */ public class VDateFieldCalendar extends VDateField { - private final VCalendarPanel calendarPanel; + protected final VCalendarPanel calendarPanel; public VDateFieldCalendar() { super(); @@ -44,73 +40,11 @@ public class VDateFieldCalendar extends VDateField { }); } - @Override - @SuppressWarnings("deprecation") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - super.updateFromUIDL(uidl, client); - calendarPanel.setShowISOWeekNumbers(isShowISOWeekNumbers()); - calendarPanel.setDateTimeService(getDateTimeService()); - calendarPanel.setResolution(getCurrentResolution()); - Date currentDate = getCurrentDate(); - if (currentDate != null) { - calendarPanel.setDate(new Date(currentDate.getTime())); - } else { - calendarPanel.setDate(null); - } - - if (currentResolution > RESOLUTION_DAY) { - calendarPanel.setTimeChangeListener(new TimeChangeListener() { - public void changed(int hour, int min, int sec, int msec) { - Date d = getDate(); - if (d == null) { - // date currently null, use the value from calendarPanel - // (~ client time at the init of the widget) - d = (Date) calendarPanel.getDate().clone(); - } - d.setHours(hour); - d.setMinutes(min); - d.setSeconds(sec); - DateTimeService.setMilliseconds(d, msec); - - // Always update time changes to the server - calendarPanel.setDate(d); - updateValueFromPanel(); - } - }); - } - - if (currentResolution <= RESOLUTION_MONTH) { - calendarPanel.setFocusChangeListener(new FocusChangeListener() { - public void focusChanged(Date date) { - Date date2 = new Date(); - if (calendarPanel.getDate() != null) { - date2.setTime(calendarPanel.getDate().getTime()); - } - /* - * Update the value of calendarPanel - */ - date2.setYear(date.getYear()); - date2.setMonth(date.getMonth()); - calendarPanel.setDate(date2); - /* - * Then update the value from panel to server - */ - updateValueFromPanel(); - } - }); - } else { - calendarPanel.setFocusChangeListener(null); - } - - // Update possible changes - calendarPanel.renderCalendar(); - } - /** * TODO refactor: almost same method as in VPopupCalendar.updateValue */ @SuppressWarnings("deprecation") - private void updateValueFromPanel() { + protected void updateValueFromPanel() { Date date2 = calendarPanel.getDate(); Date currentDate = getCurrentDate(); if (currentDate == null || date2.getTime() != currentDate.getTime()) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/VPopupCalendar.java index 8bf2f6bfbf..7011e5358b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/VPopupCalendar.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.datefield; import java.util.Date; @@ -21,16 +21,13 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; -import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusChangeListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.SubmitListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.FocusOutListener; +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.SubmitListener; /** * Represents a date selection component with a text field and a popup date @@ -42,19 +39,19 @@ import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener; * <code>setCalendarPanel(VCalendarPanel panel)</code> method. * */ -public class VPopupCalendar extends VTextualDate implements Paintable, Field, +public class VPopupCalendar extends VTextualDate implements Field, ClickHandler, CloseHandler<PopupPanel>, SubPartAware { - private static final String POPUP_PRIMARY_STYLE_NAME = VDateField.CLASSNAME + protected static final String POPUP_PRIMARY_STYLE_NAME = VDateField.CLASSNAME + "-popup"; - private final Button calendarToggle; + protected final Button calendarToggle; - private VCalendarPanel calendar; + protected VCalendarPanel calendar; - private final VOverlay popup; + protected final VOverlay popup; private boolean open = false; - private boolean parsable = true; + protected boolean parsable = true; public VPopupCalendar() { super(); @@ -105,7 +102,7 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, } @SuppressWarnings("deprecation") - private void updateValue(Date newDate) { + protected void updateValue(Date newDate) { Date currentDate = getCurrentDate(); if (currentDate == null || newDate.getTime() != currentDate.getTime()) { setCurrentDate((Date) newDate.clone()); @@ -126,14 +123,6 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, if (getCurrentResolution() > RESOLUTION_MIN) { getClient().updateVariable(getId(), "sec", newDate.getSeconds(), false); - if (getCurrentResolution() == RESOLUTION_MSEC) { - getClient().updateVariable( - getId(), - "msec", - DateTimeService - .getMilliseconds(newDate), - false); - } } } } @@ -149,96 +138,6 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, * (non-Javadoc) * * @see - * com.vaadin.terminal.gwt.client.ui.VTextualDate#updateFromUIDL(com.vaadin - * .terminal.gwt.client.UIDL, - * com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - @Override - @SuppressWarnings("deprecation") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - boolean lastReadOnlyState = readonly; - boolean lastEnabledState = isEnabled(); - - parsable = uidl.getBooleanAttribute("parsable"); - - super.updateFromUIDL(uidl, client); - - String popupStyleNames = ApplicationConnection.getStyleName( - POPUP_PRIMARY_STYLE_NAME, uidl, false); - popupStyleNames += " " + VDateField.CLASSNAME + "-" - + resolutionToString(currentResolution); - popup.setStyleName(popupStyleNames); - - calendar.setDateTimeService(getDateTimeService()); - calendar.setShowISOWeekNumbers(isShowISOWeekNumbers()); - if (calendar.getResolution() != currentResolution) { - calendar.setResolution(currentResolution); - if (calendar.getDate() != null) { - calendar.setDate((Date) getCurrentDate().clone()); - // force re-render when changing resolution only - calendar.renderCalendar(); - } - } - calendarToggle.setEnabled(enabled); - - if (currentResolution <= RESOLUTION_MONTH) { - calendar.setFocusChangeListener(new FocusChangeListener() { - public void focusChanged(Date date) { - updateValue(date); - buildDate(); - Date date2 = calendar.getDate(); - date2.setYear(date.getYear()); - date2.setMonth(date.getMonth()); - } - }); - } else { - calendar.setFocusChangeListener(null); - } - - if (currentResolution > RESOLUTION_DAY) { - calendar.setTimeChangeListener(new TimeChangeListener() { - public void changed(int hour, int min, int sec, int msec) { - Date d = getDate(); - if (d == null) { - // date currently null, use the value from calendarPanel - // (~ client time at the init of the widget) - d = (Date) calendar.getDate().clone(); - } - d.setHours(hour); - d.setMinutes(min); - d.setSeconds(sec); - DateTimeService.setMilliseconds(d, msec); - - // Always update time changes to the server - updateValue(d); - - // Update text field - buildDate(); - } - }); - } - - if (readonly) { - calendarToggle.addStyleName(CLASSNAME + "-button-readonly"); - } else { - calendarToggle.removeStyleName(CLASSNAME + "-button-readonly"); - } - - if (lastReadOnlyState != readonly || lastEnabledState != isEnabled()) { - // Enabled or readonly state changed. Differences in theming might - // affect the width (for instance if the popup button is hidden) so - // we have to recalculate the width (IF the width of the field is - // fixed) - updateWidth(); - } - - calendarToggle.setEnabled(true); - } - - /* - * (non-Javadoc) - * - * @see * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String) */ @Override @@ -276,7 +175,7 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, int l = calendarToggle.getAbsoluteLeft(); // Add a little extra space to the right to avoid - // problems with IE6/IE7 scrollbars and to make it look + // problems with IE7 scrollbars and to make it look // nicer. int extraSpace = 30; @@ -381,20 +280,6 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, /* * (non-Javadoc) * - * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#getFieldExtraWidth() - */ - @Override - protected int getFieldExtraWidth() { - if (fieldExtraWidth < 0) { - fieldExtraWidth = super.getFieldExtraWidth(); - fieldExtraWidth += calendarToggle.getOffsetWidth(); - } - return fieldExtraWidth; - } - - /* - * (non-Javadoc) - * * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#buildDate() */ @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/VTextualDate.java index 56cdf05ddb..db4eca152a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/VTextualDate.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.datefield; import java.util.Date; @@ -12,41 +12,32 @@ import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.TextBox; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ContainerResizedListener; import com.vaadin.terminal.gwt.client.EventId; import com.vaadin.terminal.gwt.client.Focusable; import com.vaadin.terminal.gwt.client.LocaleNotLoadedException; import com.vaadin.terminal.gwt.client.LocaleService; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; -public class VTextualDate extends VDateField implements Paintable, Field, - ChangeHandler, ContainerResizedListener, Focusable, SubPartAware { +public class VTextualDate extends VDateField implements Field, ChangeHandler, + Focusable, SubPartAware { private static final String PARSE_ERROR_CLASSNAME = CLASSNAME + "-parseerror"; - private final TextBox text; + protected final TextBox text; - private String formatStr; + protected String formatStr; - private String width; - - private boolean needLayout; - - protected int fieldExtraWidth = -1; - - private boolean lenient; + protected boolean lenient; private static final String CLASSNAME_PROMPT = "prompt"; - private static final String ATTR_INPUTPROMPT = "prompt"; - private String inputPrompt = ""; + protected static final String ATTR_INPUTPROMPT = "prompt"; + protected String inputPrompt = ""; private boolean prompting = false; public VTextualDate() { @@ -94,37 +85,6 @@ public class VTextualDate extends VDateField implements Paintable, Field, add(text); } - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - int origRes = currentResolution; - String oldLocale = currentLocale; - super.updateFromUIDL(uidl, client); - if (origRes != currentResolution || oldLocale != currentLocale) { - // force recreating format string - formatStr = null; - } - if (uidl.hasAttribute("format")) { - formatStr = uidl.getStringAttribute("format"); - } - - inputPrompt = uidl.getStringAttribute(ATTR_INPUTPROMPT); - - lenient = !uidl.getBooleanAttribute("strict"); - - buildDate(); - // not a FocusWidget -> needs own tabindex handling - if (uidl.hasAttribute("tabindex")) { - text.setTabIndex(uidl.getIntAttribute("tabindex")); - } - - if (readonly) { - text.addStyleDependentName("readonly"); - } else { - text.removeStyleDependentName("readonly"); - } - - } - protected String getFormatString() { if (formatStr == null) { if (currentResolution == RESOLUTION_YEAR) { @@ -148,9 +108,6 @@ public class VTextualDate extends VDateField implements Paintable, Field, frmString += ":mm"; if (currentResolution >= RESOLUTION_SEC) { frmString += ":ss"; - if (currentResolution >= RESOLUTION_MSEC) { - frmString += ".SSS"; - } } } if (dts.isTwelveHourClock()) { @@ -300,10 +257,6 @@ public class VTextualDate extends VDateField implements Paintable, Field, currentResolution == VDateField.RESOLUTION_SEC && immediate); } - if (currentResolution == VDateField.RESOLUTION_MSEC) { - getClient().updateVariable(getId(), "msec", - currentDate != null ? getMilliseconds() : -1, immediate); - } } @@ -338,83 +291,6 @@ public class VTextualDate extends VDateField implements Paintable, Field, return format.trim(); } - @Override - public void setWidth(String newWidth) { - if (!"".equals(newWidth) && (isUndefinedWidth() || !newWidth.equals(width))) { - if (BrowserInfo.get().isIE6()) { - // in IE6 cols ~ min-width - DOM.setElementProperty(text.getElement(), "size", "1"); - } - needLayout = true; - width = newWidth; - super.setWidth(width); - iLayout(); - if (newWidth.indexOf("%") < 0) { - needLayout = false; - } - } else { - if ("".equals(newWidth) && !isUndefinedWidth()) { - // Changing from defined to undefined - if (BrowserInfo.get().isIE6()) { - // revert IE6 hack - DOM.setElementProperty(text.getElement(), "size", ""); - } - super.setWidth(""); - iLayout(true); - width = null; - } - } - } - protected boolean isUndefinedWidth() { - return width == null || "".equals(width); - } - - /** - * Returns pixels in x-axis reserved for other than textfield content. - * - * @return extra width in pixels - */ - protected int getFieldExtraWidth() { - if (fieldExtraWidth < 0) { - text.setWidth("0"); - fieldExtraWidth = text.getOffsetWidth(); - if (BrowserInfo.get().isFF3()) { - // Firefox somehow always leaves the INPUT element 2px wide - fieldExtraWidth -= 2; - } - } - return fieldExtraWidth; - } - - /** - * Force an recalculation of the width of the component IF the width has - * been defined. Does nothing if width is undefined as the width will be - * automatically adjusted by the browser. - */ - public void updateWidth() { - if (isUndefinedWidth()) { - return; - } - needLayout = true; - fieldExtraWidth = -1; - iLayout(true); - } - - public void iLayout() { - iLayout(false); - } - - public void iLayout(boolean force) { - if (needLayout || force) { - int textFieldWidth = getOffsetWidth() - getFieldExtraWidth(); - if (textFieldWidth < 0) { - // Field can never be smaller than 0 (causes exception in IE) - textFieldWidth = 0; - } - text.setWidth(textFieldWidth + "px"); - } - } - public void focus() { text.setFocus(true); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VAbstractDropHandler.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VAbstractDropHandler.java index 6280031d84..ce47c7d13a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VAbstractDropHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VAbstractDropHandler.java @@ -9,7 +9,7 @@ import com.google.gwt.user.client.Command; import com.vaadin.event.Transferable; import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; public abstract class VAbstractDropHandler implements VDropHandler { @@ -129,6 +129,6 @@ public abstract class VAbstractDropHandler implements VDropHandler { * side counterpart of the Paintable is expected to implement * {@link DropTarget} interface. */ - public abstract Paintable getPaintable(); + public abstract ComponentConnector getConnector(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java index 74ab1dcc47..2f404a3028 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java @@ -23,8 +23,9 @@ import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.ValueMap; @@ -226,7 +227,7 @@ public class VDragAndDropManager { ENTER, LEAVE, OVER, DROP } - private static final String DD_SERVICE = "DD"; + public static final String DD_SERVICE = "DD"; private static VDragAndDropManager instance; private HandlerRegistration handlerRegistration; @@ -337,10 +338,10 @@ public class VDragAndDropManager { } private void addActiveDragSourceStyleName() { - Paintable dragSource = currentDrag.getTransferable() + ComponentConnector dragSource = currentDrag.getTransferable() .getDragSource(); - ((Widget) dragSource) - .addStyleName(ACTIVE_DRAG_SOURCE_STYLENAME); + dragSource.getWidget().addStyleName( + ACTIVE_DRAG_SOURCE_STYLENAME); } }; @@ -503,8 +504,8 @@ public class VDragAndDropManager { * handled. E.g. hidden on start, removed in drophandler -> * would flicker in case removed eagerly. */ - final Paintable dragSource = currentDrag.getTransferable() - .getDragSource(); + final ComponentConnector dragSource = currentDrag + .getTransferable().getDragSource(); final ApplicationConnection client = currentDropHandler .getApplicationConnection(); Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { @@ -547,8 +548,8 @@ public class VDragAndDropManager { } - private void removeActiveDragSourceStyleName(Paintable dragSource) { - ((Widget) dragSource).removeStyleName(ACTIVE_DRAG_SOURCE_STYLENAME); + private void removeActiveDragSourceStyleName(ComponentConnector dragSource) { + dragSource.getWidget().removeStyleName(ACTIVE_DRAG_SOURCE_STYLENAME); } private void clearDragElement() { @@ -582,7 +583,7 @@ public class VDragAndDropManager { if (currentDropHandler == null) { return; } - Paintable paintable = currentDropHandler.getPaintable(); + ComponentConnector paintable = currentDropHandler.getConnector(); ApplicationConnection client = currentDropHandler .getApplicationConnection(); /* @@ -610,8 +611,9 @@ public class VDragAndDropManager { if (currentDrag.getCurrentGwtEvent() != null) { try { - MouseEventDetails mouseEventDetails = new MouseEventDetails( - currentDrag.getCurrentGwtEvent()); + MouseEventDetails mouseEventDetails = MouseEventDetailsBuilder + .buildMouseEventDetails(currentDrag + .getCurrentGwtEvent()); currentDrag.getDropDetails().put("mouseEvent", mouseEventDetails.serialize()); } catch (Exception e) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java index 1278ed7fdb..aabbf58b24 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java @@ -3,7 +3,8 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; /** @@ -16,14 +17,17 @@ final public class VDragSourceIs extends VAcceptCriterion { @Override protected boolean accept(VDragEvent drag, UIDL configuration) { try { - Paintable component = drag.getTransferable().getDragSource(); + ComponentConnector component = drag.getTransferable() + .getDragSource(); int c = configuration.getIntAttribute("c"); for (int i = 0; i < c; i++) { String requiredPid = configuration .getStringAttribute("component" + i); - Paintable paintable = VDragAndDropManager.get() - .getCurrentDropHandler().getApplicationConnection() - .getPaintable(requiredPid); + VDropHandler currentDropHandler = VDragAndDropManager.get() + .getCurrentDropHandler(); + ComponentConnector paintable = (ComponentConnector) ConnectorMap + .get(currentDropHandler.getApplicationConnection()) + .getConnector(requiredPid); if (paintable == component) { return true; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VDropHandler.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VDropHandler.java index a597e9ed30..92bd6abe61 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDropHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDropHandler.java @@ -4,7 +4,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; /** * Vaadin Widgets that want to receive something via drag and drop implement @@ -59,9 +59,9 @@ public interface VDropHandler { public void dragOver(VDragEvent currentDrag); /** - * Returns the Paintable into which this DragHandler is associated + * Returns the ComponentConnector with which this DropHandler is associated */ - public Paintable getPaintable(); + public ComponentConnector getConnector(); /** * Returns the application connection to which this {@link VDropHandler} diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VHasDropHandler.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VHasDropHandler.java index 0195862431..6d6f7c776d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VHasDropHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VHasDropHandler.java @@ -3,13 +3,13 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; /** * Used to detect Widget from widget tree that has {@link #getDropHandler()} * * Decide whether to get rid of this class. If so, {@link VAbstractDropHandler} - * must extend {@link Paintable}. + * must extend {@link ComponentConnector}. * */ public interface VHasDropHandler { diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java index 58dc0d3956..90e2b033c9 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java @@ -6,7 +6,8 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; final public class VIsOverId extends VAcceptCriterion { @@ -16,10 +17,14 @@ final public class VIsOverId extends VAcceptCriterion { try { String pid = configuration.getStringAttribute("s"); - Paintable paintable = VDragAndDropManager.get() - .getCurrentDropHandler().getPaintable(); - String pid2 = VDragAndDropManager.get().getCurrentDropHandler() - .getApplicationConnection().getPid(paintable); + VDropHandler currentDropHandler = VDragAndDropManager.get() + .getCurrentDropHandler(); + ComponentConnector dropHandlerConnector = currentDropHandler + .getConnector(); + ConnectorMap paintableMap = ConnectorMap.get(currentDropHandler + .getApplicationConnection()); + + String pid2 = dropHandlerConnector.getConnectorId(); if (pid2.equals(pid)) { Object searchedId = drag.getDropDetails().get("itemIdOver"); String[] stringArrayAttribute = configuration diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java index ada7a3c78a..eb55c1a91c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java @@ -6,7 +6,7 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; final public class VItemIdIs extends VAcceptCriterion { @@ -15,9 +15,11 @@ final public class VItemIdIs extends VAcceptCriterion { protected boolean accept(VDragEvent drag, UIDL configuration) { try { String pid = configuration.getStringAttribute("s"); - Paintable dragSource = drag.getTransferable().getDragSource(); - String pid2 = VDragAndDropManager.get().getCurrentDropHandler() - .getApplicationConnection().getPid(dragSource); + ComponentConnector dragSource = drag.getTransferable() + .getDragSource(); + VDropHandler currentDropHandler = VDragAndDropManager.get() + .getCurrentDropHandler(); + String pid2 = dragSource.getConnectorId(); if (pid2.equals(pid)) { Object searchedId = drag.getTransferable().getData("itemId"); String[] stringArrayAttribute = configuration diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java index ff4c4f1d35..430b422b34 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java @@ -6,16 +6,16 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; final public class VSourceIsTarget extends VAcceptCriterion { @Override protected boolean accept(VDragEvent drag, UIDL configuration) { - Paintable dragSource = drag.getTransferable().getDragSource(); - Paintable paintable = VDragAndDropManager.get().getCurrentDropHandler() - .getPaintable(); + ComponentConnector dragSource = drag.getTransferable().getDragSource(); + ComponentConnector paintable = VDragAndDropManager.get() + .getCurrentDropHandler().getConnector(); return paintable == dragSource; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java index e1f2e34063..f69fa85290 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java @@ -8,8 +8,8 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VTree; -import com.vaadin.terminal.gwt.client.ui.VTree.TreeNode; +import com.vaadin.terminal.gwt.client.ui.tree.VTree; +import com.vaadin.terminal.gwt.client.ui.tree.VTree.TreeNode; final public class VTargetInSubtree extends VAcceptCriterion { @@ -17,7 +17,7 @@ final public class VTargetInSubtree extends VAcceptCriterion { protected boolean accept(VDragEvent drag, UIDL configuration) { VTree tree = (VTree) VDragAndDropManager.get().getCurrentDropHandler() - .getPaintable(); + .getConnector(); TreeNode treeNode = tree.getNodeByKey((String) drag.getDropDetails() .get("itemIdOver")); if (treeNode != null) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VTransferable.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VTransferable.java index fe51ea82e4..f87da378d7 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VTransferable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VTransferable.java @@ -8,7 +8,7 @@ import java.util.HashMap; import java.util.Map; import com.vaadin.event.dd.DragSource; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ComponentConnector; /** * Client side counterpart for Transferable in com.vaadin.event.Transferable @@ -16,7 +16,7 @@ import com.vaadin.terminal.gwt.client.Paintable; */ public class VTransferable { - private Paintable component; + private ComponentConnector component; private final Map<String, Object> variables = new HashMap<String, Object>(); @@ -26,7 +26,7 @@ public class VTransferable { * * @return the component */ - public Paintable getDragSource() { + public ComponentConnector getDragSource() { return component; } @@ -41,7 +41,7 @@ public class VTransferable { * @param component * the component to set */ - public void setDragSource(Paintable component) { + public void setDragSource(ComponentConnector component) { this.component = component; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java new file mode 100644 index 0000000000..db531ac70f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java @@ -0,0 +1,75 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.draganddropwrapper; + +import java.util.HashMap; +import java.util.Set; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.customcomponent.CustomComponentConnector; +import com.vaadin.ui.DragAndDropWrapper; + +@Connect(DragAndDropWrapper.class) +public class DragAndDropWrapperConnector extends CustomComponentConnector + implements Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + if (isRealUpdate(uidl) && !uidl.hasAttribute("hidden")) { + UIDL acceptCrit = uidl.getChildByTagName("-ac"); + if (acceptCrit == null) { + getWidget().dropHandler = null; + } else { + if (getWidget().dropHandler == null) { + getWidget().dropHandler = getWidget().new CustomDropHandler(); + } + getWidget().dropHandler.updateAcceptRules(acceptCrit); + } + + Set<String> variableNames = uidl.getVariableNames(); + for (String fileId : variableNames) { + if (fileId.startsWith("rec-")) { + String receiverUrl = uidl.getStringVariable(fileId); + fileId = fileId.substring(4); + if (getWidget().fileIdToReceiver == null) { + getWidget().fileIdToReceiver = new HashMap<String, String>(); + } + if ("".equals(receiverUrl)) { + Integer id = Integer.parseInt(fileId); + int indexOf = getWidget().fileIds.indexOf(id); + if (indexOf != -1) { + getWidget().files.remove(indexOf); + getWidget().fileIds.remove(indexOf); + } + } else { + getWidget().fileIdToReceiver.put(fileId, receiverUrl); + } + } + } + getWidget().startNextUpload(); + + getWidget().dragStartMode = uidl + .getIntAttribute(VDragAndDropWrapper.DRAG_START_MODE); + getWidget().initDragStartMode(); + getWidget().html5DataFlavors = uidl + .getMapAttribute(VDragAndDropWrapper.HTML5_DATA_FLAVORS); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VDragAndDropWrapper.class); + } + + @Override + public VDragAndDropWrapper getWidget() { + return (VDragAndDropWrapper) super.getWidget(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java index ff649ebeb1..d09b81e1e1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java @@ -1,13 +1,11 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.draganddropwrapper; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArrayString; @@ -25,15 +23,15 @@ import com.google.gwt.user.client.ui.Widget; import com.google.gwt.xhr.client.ReadyStateChangeHandler; import com.google.gwt.xhr.client.XMLHttpRequest; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation; -import com.vaadin.terminal.gwt.client.RenderInformation.Size; -import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.VTooltip; import com.vaadin.terminal.gwt.client.ValueMap; +import com.vaadin.terminal.gwt.client.ui.customcomponent.VCustomComponent; import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation; import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; @@ -109,28 +107,23 @@ public class VDragAndDropWrapper extends VCustomComponent implements private boolean startDrag(NativeEvent event) { if (dragStartMode == WRAPPER || dragStartMode == COMPONENT) { VTransferable transferable = new VTransferable(); - transferable.setDragSource(VDragAndDropWrapper.this); - - Paintable paintable; - Widget w = Util.findWidget((Element) event.getEventTarget().cast(), - null); - while (w != null && !(w instanceof Paintable)) { - w = w.getParent(); - } - paintable = (Paintable) w; + transferable.setDragSource(ConnectorMap.get(client).getConnector( + VDragAndDropWrapper.this)); + ComponentConnector paintable = Util.findPaintable(client, + (Element) event.getEventTarget().cast()); + Widget widget = paintable.getWidget(); transferable.setData("component", paintable); VDragEvent dragEvent = VDragAndDropManager.get().startDrag( transferable, event, true); - transferable.setData("mouseDown", - new MouseEventDetails(event).serialize()); + transferable.setData("mouseDown", MouseEventDetailsBuilder + .buildMouseEventDetails(event).serialize()); if (dragStartMode == WRAPPER) { dragEvent.createDragImage(getElement(), true); } else { - dragEvent.createDragImage(((Widget) paintable).getElement(), - true); + dragEvent.createDragImage(widget.getElement(), true); } return true; } @@ -144,58 +137,15 @@ public class VDragAndDropWrapper extends VCustomComponent implements protected int dragStartMode; - private ApplicationConnection client; - private VAbstractDropHandler dropHandler; + ApplicationConnection client; + VAbstractDropHandler dropHandler; private VDragEvent vaadinDragEvent; - private int filecounter = 0; - private Map<String, String> fileIdToReceiver; - private ValueMap html5DataFlavors; + int filecounter = 0; + Map<String, String> fileIdToReceiver; + ValueMap html5DataFlavors; private Element dragStartElement; - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - super.updateFromUIDL(uidl, client); - if (!uidl.hasAttribute("cached") && !uidl.hasAttribute("hidden")) { - UIDL acceptCrit = uidl.getChildByTagName("-ac"); - if (acceptCrit == null) { - dropHandler = null; - } else { - if (dropHandler == null) { - dropHandler = new CustomDropHandler(); - } - dropHandler.updateAcceptRules(acceptCrit); - } - - Set<String> variableNames = uidl.getVariableNames(); - for (String fileId : variableNames) { - if (fileId.startsWith("rec-")) { - String receiverUrl = uidl.getStringVariable(fileId); - fileId = fileId.substring(4); - if (fileIdToReceiver == null) { - fileIdToReceiver = new HashMap<String, String>(); - } - if ("".equals(receiverUrl)) { - Integer id = Integer.parseInt(fileId); - int indexOf = fileIds.indexOf(id); - if (indexOf != -1) { - files.remove(indexOf); - fileIds.remove(indexOf); - } - } else { - fileIdToReceiver.put(fileId, receiverUrl); - } - } - } - startNextUpload(); - - dragStartMode = uidl.getIntAttribute(DRAG_START_MODE); - initDragStartMode(); - html5DataFlavors = uidl.getMapAttribute(HTML5_DATA_FLAVORS); - } - } - protected void initDragStartMode() { Element div = getElement(); if (dragStartMode == HTML5) { @@ -235,7 +185,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements }; private Timer dragleavetimer; - private void startNextUpload() { + void startNextUpload() { Scheduler.get().scheduleDeferred(new Command() { public void execute() { @@ -291,7 +241,8 @@ public class VDragAndDropWrapper extends VCustomComponent implements } if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) { VTransferable transferable = new VTransferable(); - transferable.setDragSource(this); + transferable.setDragSource(ConnectorMap.get(client) + .getConnector(this)); vaadinDragEvent = VDragAndDropManager.get().startDrag( transferable, event, false); @@ -459,18 +410,14 @@ public class VDragAndDropWrapper extends VCustomComponent implements * @param fileId * @param data */ - private List<Integer> fileIds = new ArrayList<Integer>(); - private List<VHtml5File> files = new ArrayList<VHtml5File>(); + List<Integer> fileIds = new ArrayList<Integer>(); + List<VHtml5File> files = new ArrayList<VHtml5File>(); private void queueFilePost(final int fileId, final VHtml5File file) { fileIds.add(fileId); files.add(file); } - private String getPid() { - return client.getPid(this); - } - public VDropHandler getDropHandler() { return dropHandler; } @@ -547,8 +494,9 @@ public class VDragAndDropWrapper extends VCustomComponent implements } @Override - public Paintable getPaintable() { - return VDragAndDropWrapper.this; + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector( + VDragAndDropWrapper.this); } public ApplicationConnection getApplicationConnection() { @@ -561,7 +509,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements /*-{ var me = this; el.addEventListener("dragstart", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); }), false); }-*/; @@ -575,19 +523,19 @@ public class VDragAndDropWrapper extends VCustomComponent implements var me = this; el.addEventListener("dragenter", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); }), false); el.addEventListener("dragleave", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); }), false); el.addEventListener("dragover", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); }), false); el.addEventListener("drop", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); }), false); }-*/; @@ -610,11 +558,6 @@ public class VDragAndDropWrapper extends VCustomComponent implements } protected void deEmphasis(boolean doLayout) { - Size size = null; - if (doLayout) { - size = new RenderInformation.Size(getOffsetWidth(), - getOffsetHeight()); - } if (emphasizedVDrop != null) { VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false); VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" @@ -623,13 +566,16 @@ public class VDragAndDropWrapper extends VCustomComponent implements + emphasizedHDrop.toString().toLowerCase(), false); } if (doLayout) { - handleVaadinRelatedSizeChange(size); + notifySizePotentiallyChanged(); } } + private void notifySizePotentiallyChanged() { + LayoutManager.get(client).setNeedsMeasure( + ConnectorMap.get(client).getConnector(getElement())); + } + protected void emphasis(VDragEvent drag) { - Size size = new RenderInformation.Size(getOffsetWidth(), - getOffsetHeight()); deEmphasis(false); VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true); VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" @@ -641,20 +587,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements // TODO build (to be an example) an emphasis mode where drag image // is fitted before or after the content - handleVaadinRelatedSizeChange(size); - - } - - protected void handleVaadinRelatedSizeChange(Size originalSize) { - if (isDynamicHeight() || isDynamicWidth()) { - if (!originalSize.equals(new RenderInformation.Size( - getOffsetWidth(), getOffsetHeight()))) { - Util.notifyParentOfSizeChange(VDragAndDropWrapper.this, false); - } - } - client.handleComponentRelativeSize(VDragAndDropWrapper.this); - Util.notifyParentOfSizeChange(this, false); - + notifySizePotentiallyChanged(); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java index 30483545e9..bb511524e5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java @@ -1,8 +1,8 @@ -/* +/* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.draganddropwrapper; import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.Document; @@ -40,7 +40,7 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { var me = this; el.attachEvent("ondragstart", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); })); }-*/; @@ -50,19 +50,19 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { var me = this; el.attachEvent("ondragenter", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); })); el.attachEvent("ondragleave", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); })); el.attachEvent("ondragover", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); })); el.attachEvent("ondrop", $entry(function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); + return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); })); }-*/; diff --git a/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedConnector.java b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedConnector.java new file mode 100644 index 0000000000..81ac195c8e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedConnector.java @@ -0,0 +1,205 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.embedded; + +import java.util.Map; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.ObjectElement; +import com.google.gwt.dom.client.Style; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.Embedded; + +@Connect(Embedded.class) +public class EmbeddedConnector extends AbstractComponentConnector implements + Paintable { + + public static final String ALTERNATE_TEXT = "alt"; + + EmbeddedServerRpc rpc; + + @Override + protected void init() { + super.init(); + rpc = RpcProxy.create(EmbeddedServerRpc.class, this); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + // Save details + getWidget().client = client; + + boolean clearBrowserElement = true; + + clickEventHandler.handleEventHandlerRegistration(); + + if (uidl.hasAttribute("type")) { + getWidget().type = uidl.getStringAttribute("type"); + if (getWidget().type.equals("image")) { + getWidget().addStyleName(VEmbedded.CLASSNAME + "-image"); + Element el = null; + boolean created = false; + NodeList<Node> nodes = getWidget().getElement().getChildNodes(); + if (nodes != null && nodes.getLength() == 1) { + Node n = nodes.getItem(0); + if (n.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) n; + if (e.getTagName().equals("IMG")) { + el = e; + } + } + } + if (el == null) { + getWidget().setHTML(""); + el = DOM.createImg(); + created = true; + DOM.sinkEvents(el, Event.ONLOAD); + } + + // Set attributes + Style style = el.getStyle(); + style.setProperty("width", getState().getWidth()); + style.setProperty("height", getState().getHeight()); + + DOM.setElementProperty(el, "src", + getWidget().getSrc(uidl, client)); + + if (uidl.hasAttribute(ALTERNATE_TEXT)) { + el.setPropertyString(ALTERNATE_TEXT, + uidl.getStringAttribute(ALTERNATE_TEXT)); + } + + if (created) { + // insert in dom late + getWidget().getElement().appendChild(el); + } + + /* + * Sink tooltip events so tooltip is displayed when hovering the + * image. + */ + getWidget().sinkEvents(VTooltip.TOOLTIP_EVENTS); + + } else if (getWidget().type.equals("browser")) { + getWidget().addStyleName(VEmbedded.CLASSNAME + "-browser"); + if (getWidget().browserElement == null) { + getWidget().setHTML( + "<iframe width=\"100%\" height=\"100%\" frameborder=\"0\"" + + " allowTransparency=\"true\" src=\"\"" + + " name=\"" + uidl.getId() + + "\"></iframe>"); + getWidget().browserElement = DOM.getFirstChild(getWidget() + .getElement()); + } + DOM.setElementAttribute(getWidget().browserElement, "src", + getWidget().getSrc(uidl, client)); + clearBrowserElement = false; + } else { + VConsole.log("Unknown Embedded type '" + getWidget().type + "'"); + } + } else if (uidl.hasAttribute("mimetype")) { + final String mime = uidl.getStringAttribute("mimetype"); + if (mime.equals("application/x-shockwave-flash")) { + // Handle embedding of Flash + getWidget().addStyleName(VEmbedded.CLASSNAME + "-flash"); + getWidget().setHTML(getWidget().createFlashEmbed(uidl)); + + } else if (mime.equals("image/svg+xml")) { + getWidget().addStyleName(VEmbedded.CLASSNAME + "-svg"); + String data; + Map<String, String> parameters = VEmbedded.getParameters(uidl); + if (parameters.get("data") == null) { + data = getWidget().getSrc(uidl, client); + } else { + data = "data:image/svg+xml," + parameters.get("data"); + } + getWidget().setHTML(""); + ObjectElement obj = Document.get().createObjectElement(); + obj.setType(mime); + obj.setData(data); + if (!isUndefinedWidth()) { + obj.getStyle().setProperty("width", "100%"); + } + if (!isUndefinedHeight()) { + obj.getStyle().setProperty("height", "100%"); + } + if (uidl.hasAttribute("classid")) { + obj.setAttribute("classid", + uidl.getStringAttribute("classid")); + } + if (uidl.hasAttribute("codebase")) { + obj.setAttribute("codebase", + uidl.getStringAttribute("codebase")); + } + if (uidl.hasAttribute("codetype")) { + obj.setAttribute("codetype", + uidl.getStringAttribute("codetype")); + } + if (uidl.hasAttribute("archive")) { + obj.setAttribute("archive", + uidl.getStringAttribute("archive")); + } + if (uidl.hasAttribute("standby")) { + obj.setAttribute("standby", + uidl.getStringAttribute("standby")); + } + getWidget().getElement().appendChild(obj); + if (uidl.hasAttribute(ALTERNATE_TEXT)) { + obj.setInnerText(uidl.getStringAttribute(ALTERNATE_TEXT)); + } + } else { + VConsole.log("Unknown Embedded mimetype '" + mime + "'"); + } + } else { + VConsole.log("Unknown Embedded; no type or mimetype attribute"); + } + + if (clearBrowserElement) { + getWidget().browserElement = null; + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VEmbedded.class); + } + + @Override + public VEmbedded getWidget() { + return (VEmbedded) super.getWidget(); + } + + protected final ClickEventHandler clickEventHandler = new ClickEventHandler( + this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.click(mouseDetails); + } + + }; + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedServerRpc.java new file mode 100644 index 0000000000..7f36c812bc --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedServerRpc.java @@ -0,0 +1,10 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.embedded; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; + +public interface EmbeddedServerRpc extends ClickRpc, ServerRpc { +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/embedded/VEmbedded.java b/src/com/vaadin/terminal/gwt/client/ui/embedded/VEmbedded.java new file mode 100644 index 0000000000..203e7362f3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/embedded/VEmbedded.java @@ -0,0 +1,239 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.embedded; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; + +public class VEmbedded extends HTML { + public static String CLASSNAME = "v-embedded"; + + protected Element browserElement; + + protected String type; + + protected ApplicationConnection client; + + public VEmbedded() { + setStyleName(CLASSNAME); + } + + /** + * Creates the Object and Embed tags for the Flash plugin so it works + * cross-browser + * + * @param uidl + * The UIDL + * @return Tags concatenated into a string + */ + protected String createFlashEmbed(UIDL uidl) { + /* + * To ensure cross-browser compatibility we are using the twice-cooked + * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and + * inside it a EMBED for all other browsers. + */ + + StringBuilder html = new StringBuilder(); + + // Start the object tag + html.append("<object "); + + /* + * Add classid required for ActiveX to recognize the flash. This is a + * predefined value which ActiveX recognizes and must be the given + * value. More info can be found on + * http://kb2.adobe.com/cps/415/tn_4150.html. Allow user to override + * this by setting his own classid. + */ + if (uidl.hasAttribute("classid")) { + html.append("classid=\"" + + Util.escapeAttribute(uidl.getStringAttribute("classid")) + + "\" "); + } else { + html.append("classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" "); + } + + /* + * Add codebase required for ActiveX and must be exactly this according + * to http://kb2.adobe.com/cps/415/tn_4150.html to work with the above + * given classid. Again, see more info on + * http://kb2.adobe.com/cps/415/tn_4150.html. Limiting Flash version to + * 6.0.0.0 and above. Allow user to override this by setting his own + * codebase + */ + if (uidl.hasAttribute("codebase")) { + html.append("codebase=\"" + + Util.escapeAttribute(uidl.getStringAttribute("codebase")) + + "\" "); + } else { + html.append("codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0\" "); + } + + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + String height = paintable.getState().getHeight(); + String width = paintable.getState().getWidth(); + + // Add width and height + html.append("width=\"" + Util.escapeAttribute(width) + "\" "); + html.append("height=\"" + Util.escapeAttribute(height) + "\" "); + html.append("type=\"application/x-shockwave-flash\" "); + + // Codetype + if (uidl.hasAttribute("codetype")) { + html.append("codetype=\"" + + Util.escapeAttribute(uidl.getStringAttribute("codetype")) + + "\" "); + } + + // Standby + if (uidl.hasAttribute("standby")) { + html.append("standby=\"" + + Util.escapeAttribute(uidl.getStringAttribute("standby")) + + "\" "); + } + + // Archive + if (uidl.hasAttribute("archive")) { + html.append("archive=\"" + + Util.escapeAttribute(uidl.getStringAttribute("archive")) + + "\" "); + } + + // End object tag + html.append(">"); + + // Ensure we have an movie parameter + Map<String, String> parameters = getParameters(uidl); + if (parameters.get("movie") == null) { + parameters.put("movie", getSrc(uidl, client)); + } + + // Add parameters to OBJECT + for (String name : parameters.keySet()) { + html.append("<param "); + html.append("name=\"" + Util.escapeAttribute(name) + "\" "); + html.append("value=\"" + Util.escapeAttribute(parameters.get(name)) + + "\" "); + html.append("/>"); + } + + // Build inner EMBED tag + html.append("<embed "); + html.append("src=\"" + Util.escapeAttribute(getSrc(uidl, client)) + + "\" "); + html.append("width=\"" + Util.escapeAttribute(width) + "\" "); + html.append("height=\"" + Util.escapeAttribute(height) + "\" "); + html.append("type=\"application/x-shockwave-flash\" "); + + // Add the parameters to the Embed + for (String name : parameters.keySet()) { + html.append(Util.escapeAttribute(name)); + html.append("="); + html.append("\"" + Util.escapeAttribute(parameters.get(name)) + + "\""); + } + + // End embed tag + html.append("></embed>"); + + if (uidl.hasAttribute(EmbeddedConnector.ALTERNATE_TEXT)) { + html.append(uidl + .getStringAttribute(EmbeddedConnector.ALTERNATE_TEXT)); + } + + // End object tag + html.append("</object>"); + + return html.toString(); + } + + /** + * Returns a map (name -> value) of all parameters in the UIDL. + * + * @param uidl + * @return + */ + protected static Map<String, String> getParameters(UIDL uidl) { + Map<String, String> parameters = new HashMap<String, String>(); + + Iterator<Object> childIterator = uidl.getChildIterator(); + while (childIterator.hasNext()) { + + Object child = childIterator.next(); + if (child instanceof UIDL) { + + UIDL childUIDL = (UIDL) child; + if (childUIDL.getTag().equals("embeddedparam")) { + String name = childUIDL.getStringAttribute("name"); + String value = childUIDL.getStringAttribute("value"); + parameters.put(name, value); + } + } + + } + + return parameters; + } + + /** + * Helper to return translated src-attribute from embedded's UIDL + * + * @param uidl + * @param client + * @return + */ + protected String getSrc(UIDL uidl, ApplicationConnection client) { + String url = client.translateVaadinUri(uidl.getStringAttribute("src")); + if (url == null) { + return ""; + } + return url; + } + + @Override + protected void onDetach() { + if (BrowserInfo.get().isIE()) { + // Force browser to fire unload event when component is detached + // from the view (IE doesn't do this automatically) + if (browserElement != null) { + /* + * src was previously set to javascript:false, but this was not + * enough to overcome a bug when detaching an iframe with a pdf + * loaded in IE9. about:blank seems to cause the adobe reader + * plugin to unload properly before the iframe is removed. See + * #7855 + */ + DOM.setElementAttribute(browserElement, "src", "about:blank"); + } + } + super.onDetach(); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONLOAD) { + VConsole.log("Embeddable onload"); + Util.notifyParentOfSizeChange(this, true); + } + + client.handleTooltipEvent(event, this); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/FormConnector.java b/src/com/vaadin/terminal/gwt/client/ui/form/FormConnector.java new file mode 100644 index 0000000000..82cbc95b2d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/form/FormConnector.java @@ -0,0 +1,209 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.form; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener; +import com.vaadin.terminal.gwt.client.ui.layout.MayScrollChildren; +import com.vaadin.ui.Form; + +@Connect(Form.class) +public class FormConnector extends AbstractComponentContainerConnector + implements Paintable, MayScrollChildren { + + private final ElementResizeListener footerResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + VForm form = getWidget(); + + int footerHeight; + if (form.footer != null) { + LayoutManager lm = getLayoutManager(); + footerHeight = lm.getOuterHeight(form.footer.getElement()); + } else { + footerHeight = 0; + } + + form.fieldContainer.getStyle().setPaddingBottom(footerHeight, + Unit.PX); + form.footerContainer.getStyle() + .setMarginTop(-footerHeight, Unit.PX); + } + }; + + @Override + public void onUnregister() { + VForm form = getWidget(); + if (form.footer != null) { + getLayoutManager().removeElementResizeListener( + form.footer.getElement(), footerResizeListener); + } + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + boolean legendEmpty = true; + if (getState().getCaption() != null) { + getWidget().caption.setInnerText(getState().getCaption()); + legendEmpty = false; + } else { + getWidget().caption.setInnerText(""); + } + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(client); + getWidget().legend.insertFirst(getWidget().icon.getElement()); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + legendEmpty = false; + } else { + if (getWidget().icon != null) { + getWidget().legend.removeChild(getWidget().icon.getElement()); + } + } + if (legendEmpty) { + getWidget().addStyleDependentName("nocaption"); + } else { + getWidget().removeStyleDependentName("nocaption"); + } + + if (null != getState().getErrorMessage()) { + getWidget().errorMessage + .updateMessage(getState().getErrorMessage()); + getWidget().errorMessage.setVisible(true); + } else { + getWidget().errorMessage.setVisible(false); + } + + if (getState().hasDescription()) { + getWidget().desc.setInnerHTML(getState().getDescription()); + if (getWidget().desc.getParentElement() == null) { + getWidget().fieldSet.insertAfter(getWidget().desc, + getWidget().legend); + } + } else { + getWidget().desc.setInnerHTML(""); + if (getWidget().desc.getParentElement() != null) { + getWidget().fieldSet.removeChild(getWidget().desc); + } + } + + // first render footer so it will be easier to handle relative height of + // main layout + if (getState().getFooter() != null) { + // render footer + ComponentConnector newFooter = (ComponentConnector) getState() + .getFooter(); + Widget newFooterWidget = newFooter.getWidget(); + if (getWidget().footer == null) { + getLayoutManager().addElementResizeListener( + newFooterWidget.getElement(), footerResizeListener); + getWidget().add(newFooter.getWidget(), + getWidget().footerContainer); + getWidget().footer = newFooterWidget; + } else if (newFooter != getWidget().footer) { + getLayoutManager().removeElementResizeListener( + getWidget().footer.getElement(), footerResizeListener); + getLayoutManager().addElementResizeListener( + newFooterWidget.getElement(), footerResizeListener); + getWidget().remove(getWidget().footer); + getWidget().add(newFooter.getWidget(), + getWidget().footerContainer); + } + getWidget().footer = newFooterWidget; + } else { + if (getWidget().footer != null) { + getLayoutManager().removeElementResizeListener( + getWidget().footer.getElement(), footerResizeListener); + getWidget().remove(getWidget().footer); + getWidget().footer = null; + } + } + + ComponentConnector newLayout = (ComponentConnector) getState() + .getLayout(); + Widget newLayoutWidget = newLayout.getWidget(); + if (getWidget().lo == null) { + // Layout not rendered before + getWidget().lo = newLayoutWidget; + getWidget().add(newLayoutWidget, getWidget().fieldContainer); + } else if (getWidget().lo != newLayoutWidget) { + // Layout has changed + getWidget().remove(getWidget().lo); + getWidget().lo = newLayoutWidget; + getWidget().add(newLayoutWidget, getWidget().fieldContainer); + } + + // also recalculates size of the footer if undefined size form - see + // #3710 + client.runDescendentsLayout(getWidget()); + + // We may have actions attached + if (uidl.getChildCount() >= 1) { + UIDL childUidl = uidl.getChildByTagName("actions"); + if (childUidl != null) { + if (getWidget().shortcutHandler == null) { + getWidget().shortcutHandler = new ShortcutActionHandler( + getConnectorId(), client); + getWidget().keyDownRegistration = getWidget() + .addDomHandler(getWidget(), KeyDownEvent.getType()); + } + getWidget().shortcutHandler.updateActionMap(childUidl); + } + } else if (getWidget().shortcutHandler != null) { + getWidget().keyDownRegistration.removeHandler(); + getWidget().shortcutHandler = null; + getWidget().keyDownRegistration = null; + } + } + + public void updateCaption(ComponentConnector component) { + // NOP form don't render caption for neither field layout nor footer + // layout + } + + @Override + public VForm getWidget() { + return (VForm) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VForm.class); + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().isPropertyReadOnly(); + } + + @Override + public FormState getState() { + return (FormState) super.getState(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/FormState.java b/src/com/vaadin/terminal/gwt/client/ui/form/FormState.java new file mode 100644 index 0000000000..c1acc0971d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/form/FormState.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.form; + +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.Connector; + +public class FormState extends AbstractFieldState { + private Connector layout; + private Connector footer; + + public Connector getLayout() { + return layout; + } + + public void setLayout(Connector layout) { + this.layout = layout; + } + + public Connector getFooter() { + return footer; + } + + public void setFooter(Connector footer) { + this.footer = footer; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java new file mode 100644 index 0000000000..e3a0c9b321 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java @@ -0,0 +1,80 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.form; + +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.VErrorMessage; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; + +public class VForm extends ComplexPanel implements KeyDownHandler { + + protected String id; + + public static final String CLASSNAME = "v-form"; + + Widget lo; + Element legend = DOM.createLegend(); + Element caption = DOM.createSpan(); + private Element errorIndicatorElement = DOM.createDiv(); + Element desc = DOM.createDiv(); + Icon icon; + VErrorMessage errorMessage = new VErrorMessage(); + + Element fieldContainer = DOM.createDiv(); + + Element footerContainer = DOM.createDiv(); + + Element fieldSet = DOM.createFieldSet(); + + Widget footer; + + ApplicationConnection client; + + ShortcutActionHandler shortcutHandler; + + HandlerRegistration keyDownRegistration; + + public VForm() { + setElement(DOM.createDiv()); + getElement().appendChild(fieldSet); + setStyleName(CLASSNAME); + fieldSet.appendChild(legend); + legend.appendChild(caption); + errorIndicatorElement.setClassName("v-errorindicator"); + errorIndicatorElement.getStyle().setDisplay(Display.NONE); + errorIndicatorElement.setInnerText(" "); // needed for IE + desc.setClassName("v-form-description"); + fieldSet.appendChild(desc); // Adding description for initial padding + // measurements, removed later if no + // description is set + fieldContainer.setClassName(CLASSNAME + "-content"); + fieldSet.appendChild(fieldContainer); + errorMessage.setVisible(false); + errorMessage.setStyleName(CLASSNAME + "-errormessage"); + fieldSet.appendChild(errorMessage.getElement()); + fieldSet.appendChild(footerContainer); + } + + public void onKeyDown(KeyDownEvent event) { + shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent())); + } + + @Override + protected void add(Widget child, Element container) { + // Overridden to allow VFormPaintable to call this. Should be removed + // once functionality from VFormPaintable is moved to VForm. + super.add(child, container); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java new file mode 100644 index 0000000000..d7774b66ef --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java @@ -0,0 +1,105 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.formlayout; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.formlayout.VFormLayout.Caption; +import com.vaadin.terminal.gwt.client.ui.formlayout.VFormLayout.ErrorFlag; +import com.vaadin.terminal.gwt.client.ui.formlayout.VFormLayout.VFormLayoutTable; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutState; +import com.vaadin.ui.FormLayout; + +@Connect(FormLayout.class) +public class FormLayoutConnector extends AbstractLayoutConnector { + + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + VFormLayoutTable formLayoutTable = getWidget().table; + + formLayoutTable.setMargins(new VMarginInfo(getState() + .getMarginsBitmask())); + formLayoutTable.setSpacing(getState().isSpacing()); + + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + VFormLayout formLayout = getWidget(); + VFormLayoutTable formLayoutTable = getWidget().table; + + int childId = 0; + + formLayoutTable.setRowCount(getChildren().size()); + + for (ComponentConnector child : getChildren()) { + Widget childWidget = child.getWidget(); + + Caption caption = formLayoutTable.getCaption(childWidget); + if (caption == null) { + caption = formLayout.new Caption(child); + caption.addClickHandler(formLayoutTable); + } + + ErrorFlag error = formLayoutTable.getError(childWidget); + if (error == null) { + error = formLayout.new ErrorFlag(child); + } + + formLayoutTable.setChild(childId, childWidget, caption, error); + childId++; + } + + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() == this) { + continue; + } + + formLayoutTable.cleanReferences(oldChild.getWidget()); + } + + } + + public void updateCaption(ComponentConnector component) { + getWidget().table.updateCaption(component.getWidget(), + component.getState(), component.isEnabled()); + boolean hideErrors = false; + + // FIXME This incorrectly depends on AbstractFieldConnector + if (component instanceof AbstractFieldConnector) { + hideErrors = ((AbstractFieldConnector) component).getState() + .isHideErrors(); + } + + getWidget().table.updateError(component.getWidget(), component + .getState().getErrorMessage(), hideErrors); + } + + @Override + public VFormLayout getWidget() { + return (VFormLayout) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VFormLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java b/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java new file mode 100644 index 0000000000..85584755a6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java @@ -0,0 +1,379 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.formlayout; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.StyleConstants; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; + +/** + * Two col Layout that places caption on left col and field on right col + */ +public class VFormLayout extends SimplePanel { + + private final static String CLASSNAME = "v-formlayout"; + + ApplicationConnection client; + VFormLayoutTable table; + + public VFormLayout() { + super(); + setStyleName(CLASSNAME); + table = new VFormLayoutTable(); + setWidget(table); + } + + /** + * Parses the stylenames from shared state + * + * @param state + * shared state of the component + * @param enabled + * @return An array of stylenames + */ + private String[] getStylesFromState(ComponentState state, boolean enabled) { + List<String> styles = new ArrayList<String>(); + if (state.hasStyles()) { + for (String name : state.getStyles()) { + styles.add(name); + } + } + + if (!enabled) { + styles.add(ApplicationConnection.DISABLED_CLASSNAME); + } + + return styles.toArray(new String[styles.size()]); + } + + public class VFormLayoutTable extends FlexTable implements ClickHandler { + + private static final int COLUMN_CAPTION = 0; + private static final int COLUMN_ERRORFLAG = 1; + private static final int COLUMN_WIDGET = 2; + + private HashMap<Widget, Caption> widgetToCaption = new HashMap<Widget, Caption>(); + private HashMap<Widget, ErrorFlag> widgetToError = new HashMap<Widget, ErrorFlag>(); + + public VFormLayoutTable() { + DOM.setElementProperty(getElement(), "cellPadding", "0"); + DOM.setElementProperty(getElement(), "cellSpacing", "0"); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt + * .event.dom.client.ClickEvent) + */ + public void onClick(ClickEvent event) { + Caption caption = (Caption) event.getSource(); + if (caption.getOwner() != null) { + if (caption.getOwner() instanceof Focusable) { + ((Focusable) caption.getOwner()).focus(); + } else if (caption.getOwner() instanceof com.google.gwt.user.client.ui.Focusable) { + ((com.google.gwt.user.client.ui.Focusable) caption + .getOwner()).setFocus(true); + } + } + } + + public void setMargins(VMarginInfo margins) { + Element margin = getElement(); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, + margins.hasTop()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, + margins.hasRight()); + setStyleName(margin, + CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, + margins.hasBottom()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, + margins.hasLeft()); + + } + + public void setSpacing(boolean spacing) { + setStyleName(getElement(), CLASSNAME + "-" + "spacing", spacing); + + } + + public void setRowCount(int rowNr) { + for (int i = 0; i < rowNr; i++) { + prepareCell(i, COLUMN_CAPTION); + getCellFormatter().setStyleName(i, COLUMN_CAPTION, + CLASSNAME + "-captioncell"); + + prepareCell(i, 1); + getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG, + CLASSNAME + "-errorcell"); + + prepareCell(i, 2); + getCellFormatter().setStyleName(i, COLUMN_WIDGET, + CLASSNAME + "-contentcell"); + + String rowstyles = CLASSNAME + "-row"; + if (i == 0) { + rowstyles += " " + CLASSNAME + "-firstrow"; + } + if (i == rowNr - 1) { + rowstyles += " " + CLASSNAME + "-lastrow"; + } + + getRowFormatter().setStyleName(i, rowstyles); + + } + while (getRowCount() != rowNr) { + removeRow(rowNr); + } + } + + public void setChild(int rowNr, Widget childWidget, Caption caption, + ErrorFlag error) { + setWidget(rowNr, COLUMN_WIDGET, childWidget); + setWidget(rowNr, COLUMN_CAPTION, caption); + setWidget(rowNr, COLUMN_ERRORFLAG, error); + + widgetToCaption.put(childWidget, caption); + widgetToError.put(childWidget, error); + + } + + public Caption getCaption(Widget childWidget) { + return widgetToCaption.get(childWidget); + } + + public ErrorFlag getError(Widget childWidget) { + return widgetToError.get(childWidget); + } + + public void cleanReferences(Widget oldChildWidget) { + widgetToError.remove(oldChildWidget); + widgetToCaption.remove(oldChildWidget); + + } + + public void updateCaption(Widget widget, ComponentState state, + boolean enabled) { + final Caption c = widgetToCaption.get(widget); + if (c != null) { + c.updateCaption(state, enabled); + } + } + + public void updateError(Widget widget, String errorMessage, + boolean hideErrors) { + final ErrorFlag e = widgetToError.get(widget); + if (e != null) { + e.updateError(errorMessage, hideErrors); + } + + } + + } + + // TODO why duplicated here? + public class Caption extends HTML { + + public static final String CLASSNAME = "v-caption"; + + private final ComponentConnector owner; + + private Element requiredFieldIndicator; + + private Icon icon; + + private Element captionText; + + /** + * + * @param component + * optional owner of caption. If not set, getOwner will + * return null + */ + public Caption(ComponentConnector component) { + super(); + owner = component; + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + private void setStyles(String[] styles) { + String styleName = CLASSNAME; + + if (styles != null) { + for (String style : styles) { + if (ApplicationConnection.DISABLED_CLASSNAME.equals(style)) { + // Add v-disabled also without classname prefix so + // generic v-disabled CSS rules work + styleName += " " + style; + } + + styleName += " " + CLASSNAME + "-" + style; + } + } + + setStyleName(styleName); + } + + public void updateCaption(ComponentState state, boolean enabled) { + // Update styles as they might have changed when the caption changed + setStyles(getStylesFromState(state, enabled)); + + boolean isEmpty = true; + + if (state.getIcon() != null) { + if (icon == null) { + icon = new Icon(owner.getConnection()); + + DOM.insertChild(getElement(), icon.getElement(), 0); + } + icon.setUri(state.getIcon().getURL()); + isEmpty = false; + } else { + if (icon != null) { + DOM.removeChild(getElement(), icon.getElement()); + icon = null; + } + + } + + if (state.getCaption() != null) { + if (captionText == null) { + captionText = DOM.createSpan(); + DOM.insertChild(getElement(), captionText, icon == null ? 0 + : 1); + } + String c = state.getCaption(); + if (c == null) { + c = ""; + } else { + isEmpty = false; + } + DOM.setInnerText(captionText, c); + } else { + // TODO should span also be removed + } + + if (state.hasDescription() && captionText != null) { + addStyleDependentName("hasdescription"); + } else { + removeStyleDependentName("hasdescription"); + } + + boolean required = owner instanceof AbstractFieldConnector + && ((AbstractFieldConnector) owner).isRequired(); + if (required) { + if (requiredFieldIndicator == null) { + requiredFieldIndicator = DOM.createSpan(); + DOM.setInnerText(requiredFieldIndicator, "*"); + DOM.setElementProperty(requiredFieldIndicator, "className", + "v-required-field-indicator"); + DOM.appendChild(getElement(), requiredFieldIndicator); + } + } else { + if (requiredFieldIndicator != null) { + DOM.removeChild(getElement(), requiredFieldIndicator); + requiredFieldIndicator = null; + } + } + + // Workaround for IE weirdness, sometimes returns bad height in some + // circumstances when Caption is empty. See #1444 + // IE7 bugs more often. I wonder what happens when IE8 arrives... + // FIXME: This could be unnecessary for IE8+ + if (BrowserInfo.get().isIE()) { + if (isEmpty) { + setHeight("0px"); + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + } else { + setHeight(""); + DOM.setStyleAttribute(getElement(), "overflow", ""); + } + + } + + } + + /** + * Returns Paintable for which this Caption belongs to. + * + * @return owner Widget + */ + public ComponentConnector getOwner() { + return owner; + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + owner.getConnection().handleTooltipEvent(event, owner); + } + } + + class ErrorFlag extends HTML { + private static final String CLASSNAME = VFormLayout.CLASSNAME + + "-error-indicator"; + Element errorIndicatorElement; + + private ComponentConnector owner; + + public ErrorFlag(ComponentConnector owner) { + setStyleName(CLASSNAME); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + this.owner = owner; + } + + public void updateError(String errorMessage, boolean hideErrors) { + boolean showError = null != errorMessage; + if (hideErrors) { + showError = false; + } + + if (showError) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setInnerHTML(errorIndicatorElement, " "); + DOM.setElementProperty(errorIndicatorElement, "className", + "v-errorindicator"); + DOM.appendChild(getElement(), errorIndicatorElement); + } + + } else if (errorIndicatorElement != null) { + DOM.removeChild(getElement(), errorIndicatorElement); + errorIndicatorElement = null; + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (owner != null) { + client.handleTooltipEvent(event, owner); + } + } + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java new file mode 100644 index 0000000000..e4a31b96ef --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java @@ -0,0 +1,239 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.gridlayout; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout.Cell; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; +import com.vaadin.ui.GridLayout; + +@Connect(GridLayout.class) +public class GridLayoutConnector extends AbstractComponentContainerConnector + implements Paintable, DirectionalManagedLayout { + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return getWidget().getComponent(element); + } + + @Override + protected LayoutClickRpc getLayoutClickRPC() { + return rpc; + }; + + }; + + private GridLayoutServerRpc rpc; + private boolean needCaptionUpdate = false; + + @Override + public void init() { + rpc = RpcProxy.create(GridLayoutServerRpc.class, this); + getLayoutManager().registerDependency(this, + getWidget().spacingMeasureElement); + } + + @Override + public void onUnregister() { + VGridLayout layout = getWidget(); + getLayoutManager().unregisterDependency(this, + layout.spacingMeasureElement); + + // Unregister caption size dependencies + for (ComponentConnector child : getChildren()) { + Cell cell = layout.widgetToCell.get(child.getWidget()); + cell.slot.setCaption(null); + } + } + + @Override + public GridLayoutState getState() { + return (GridLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + clickEventHandler.handleEventHandlerRegistration(); + + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + VGridLayout layout = getWidget(); + layout.client = client; + + if (!isRealUpdate(uidl)) { + return; + } + + int cols = getState().getColumns(); + int rows = getState().getRows(); + + layout.columnWidths = new int[cols]; + layout.rowHeights = new int[rows]; + + layout.setSize(rows, cols); + + final int[] alignments = uidl.getIntArrayAttribute("alignments"); + int alignmentIndex = 0; + + for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) { + final UIDL r = (UIDL) i.next(); + if ("gr".equals(r.getTag())) { + for (final Iterator<?> j = r.getChildIterator(); j.hasNext();) { + final UIDL cellUidl = (UIDL) j.next(); + if ("gc".equals(cellUidl.getTag())) { + int row = cellUidl.getIntAttribute("y"); + int col = cellUidl.getIntAttribute("x"); + + Widget previousWidget = null; + + Cell cell = layout.getCell(row, col); + if (cell != null && cell.slot != null) { + // This is an update. Track if the widget changes + // and update the caption if that happens. This + // workaround can be removed once the DOM update is + // done in onContainerHierarchyChange + previousWidget = cell.slot.getWidget(); + } + + cell = layout.createCell(row, col); + + cell.updateFromUidl(cellUidl); + + if (cell.hasContent()) { + cell.setAlignment(new AlignmentInfo( + alignments[alignmentIndex++])); + if (cell.slot.getWidget() != previousWidget) { + // Widget changed or widget moved from another + // slot. Update its caption as the widget might + // have called updateCaption when the widget was + // still in its old slot. This workaround can be + // removed once the DOM update + // is done in onContainerHierarchyChange + updateCaption(ConnectorMap.get(getConnection()) + .getConnector(cell.slot.getWidget())); + } + } + } + } + } + } + + layout.colExpandRatioArray = uidl.getIntArrayAttribute("colExpand"); + layout.rowExpandRatioArray = uidl.getIntArrayAttribute("rowExpand"); + + layout.updateMarginStyleNames(new VMarginInfo(getState() + .getMarginsBitmask())); + + layout.updateSpacingStyleName(getState().isSpacing()); + + if (needCaptionUpdate) { + needCaptionUpdate = false; + + for (ComponentConnector child : getChildren()) { + updateCaption(child); + } + } + getLayoutManager().setNeedsLayout(this); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + VGridLayout layout = getWidget(); + + // clean non rendered components + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() == this) { + continue; + } + + Widget childWidget = oldChild.getWidget(); + layout.remove(childWidget); + + Cell cell = layout.widgetToCell.remove(childWidget); + cell.slot.setCaption(null); + cell.slot.getWrapperElement().removeFromParent(); + cell.slot = null; + } + + } + + public void updateCaption(ComponentConnector childConnector) { + if (!childConnector.delegateCaptionHandling()) { + // Check not required by interface but by workarounds in this class + // when updateCaption is explicitly called for all children. + return; + } + + VGridLayout layout = getWidget(); + Cell cell = layout.widgetToCell.get(childConnector.getWidget()); + if (cell == null) { + // workaround before updateFromUidl is removed. We currently update + // the captions at the end of updateFromUidl instead of immediately + // because the DOM has not been set up at this point (as it is done + // in updateFromUidl) + needCaptionUpdate = true; + return; + } + if (VCaption.isNeeded(childConnector.getState())) { + VLayoutSlot layoutSlot = cell.slot; + VCaption caption = layoutSlot.getCaption(); + if (caption == null) { + caption = new VCaption(childConnector, getConnection()); + + Widget widget = childConnector.getWidget(); + + layout.setCaption(widget, caption); + } + caption.updateCaption(); + } else { + layout.setCaption(childConnector.getWidget(), null); + } + } + + @Override + public VGridLayout getWidget() { + return (VGridLayout) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VGridLayout.class); + } + + public void layoutVertically() { + getWidget().updateHeight(); + } + + public void layoutHorizontally() { + getWidget().updateWidth(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutServerRpc.java new file mode 100644 index 0000000000..cd8df297ec --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutServerRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.gridlayout; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; + +public interface GridLayoutServerRpc extends LayoutClickRpc, ServerRpc { + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutState.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutState.java new file mode 100644 index 0000000000..109dc7dea6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutState.java @@ -0,0 +1,37 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.gridlayout; + +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutState; + +public class GridLayoutState extends AbstractLayoutState { + private boolean spacing = false; + private int rows = 0; + private int columns = 0; + + public boolean isSpacing() { + return spacing; + } + + public void setSpacing(boolean spacing) { + this.spacing = spacing; + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = rows; + } + + public int getColumns() { + return columns; + } + + public void setColumns(int cols) { + columns = cols; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/VGridLayout.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/VGridLayout.java new file mode 100644 index 0000000000..7629e09cac --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/VGridLayout.java @@ -0,0 +1,702 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.gridlayout; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.layout.ComponentConnectorLayoutSlot; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; + +public class VGridLayout extends ComplexPanel { + + public static final String CLASSNAME = "v-gridlayout"; + + ApplicationConnection client; + + HashMap<Widget, Cell> widgetToCell = new HashMap<Widget, Cell>(); + + int[] columnWidths; + int[] rowHeights; + + int[] colExpandRatioArray; + + int[] rowExpandRatioArray; + + int[] minColumnWidths; + + private int[] minRowHeights; + + DivElement spacingMeasureElement; + + public VGridLayout() { + super(); + setElement(Document.get().createDivElement()); + + spacingMeasureElement = Document.get().createDivElement(); + Style spacingStyle = spacingMeasureElement.getStyle(); + spacingStyle.setPosition(Position.ABSOLUTE); + getElement().appendChild(spacingMeasureElement); + + setStyleName(CLASSNAME); + } + + private GridLayoutConnector getConnector() { + return (GridLayoutConnector) ConnectorMap.get(client) + .getConnector(this); + } + + /** + * Returns the column widths measured in pixels + * + * @return + */ + protected int[] getColumnWidths() { + return columnWidths; + } + + /** + * Returns the row heights measured in pixels + * + * @return + */ + protected int[] getRowHeights() { + return rowHeights; + } + + /** + * Returns the spacing between the cells horizontally in pixels + * + * @return + */ + protected int getHorizontalSpacing() { + return LayoutManager.get(client).getOuterWidth(spacingMeasureElement); + } + + /** + * Returns the spacing between the cells vertically in pixels + * + * @return + */ + protected int getVerticalSpacing() { + return LayoutManager.get(client).getOuterHeight(spacingMeasureElement); + } + + static int[] cloneArray(int[] toBeCloned) { + int[] clone = new int[toBeCloned.length]; + for (int i = 0; i < clone.length; i++) { + clone[i] = toBeCloned[i] * 1; + } + return clone; + } + + void expandRows() { + if (!isUndefinedHeight()) { + int usedSpace = minRowHeights[0]; + int verticalSpacing = getVerticalSpacing(); + for (int i = 1; i < minRowHeights.length; i++) { + usedSpace += verticalSpacing + minRowHeights[i]; + } + int availableSpace = LayoutManager.get(client).getInnerHeight( + getElement()); + int excessSpace = availableSpace - usedSpace; + int distributed = 0; + if (excessSpace > 0) { + for (int i = 0; i < rowHeights.length; i++) { + int ew = excessSpace * rowExpandRatioArray[i] / 1000; + rowHeights[i] = minRowHeights[i] + ew; + distributed += ew; + } + excessSpace -= distributed; + int c = 0; + while (excessSpace > 0) { + rowHeights[c % rowHeights.length]++; + excessSpace--; + c++; + } + } + } + } + + void updateHeight() { + // Detect minimum heights & calculate spans + detectRowHeights(); + + // Expand + expandRows(); + + // Position + layoutCellsVertically(); + } + + void updateWidth() { + // Detect widths & calculate spans + detectColWidths(); + // Expand + expandColumns(); + // Position + layoutCellsHorizontally(); + + } + + void expandColumns() { + if (!isUndefinedWidth()) { + int usedSpace = minColumnWidths[0]; + int horizontalSpacing = getHorizontalSpacing(); + for (int i = 1; i < minColumnWidths.length; i++) { + usedSpace += horizontalSpacing + minColumnWidths[i]; + } + + int availableSpace = LayoutManager.get(client).getInnerWidth( + getElement()); + int excessSpace = availableSpace - usedSpace; + int distributed = 0; + if (excessSpace > 0) { + for (int i = 0; i < columnWidths.length; i++) { + int ew = excessSpace * colExpandRatioArray[i] / 1000; + columnWidths[i] = minColumnWidths[i] + ew; + distributed += ew; + } + excessSpace -= distributed; + int c = 0; + while (excessSpace > 0) { + columnWidths[c % columnWidths.length]++; + excessSpace--; + c++; + } + } + } + } + + void layoutCellsVertically() { + int verticalSpacing = getVerticalSpacing(); + LayoutManager layoutManager = LayoutManager.get(client); + Element element = getElement(); + int paddingTop = layoutManager.getPaddingTop(element); + int paddingBottom = layoutManager.getPaddingBottom(element); + int y = paddingTop; + + for (int i = 0; i < cells.length; i++) { + y = paddingTop; + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + int reservedMargin; + if (cell.rowspan + j >= cells[i].length) { + // Make room for layout padding for cells reaching the + // bottom of the layout + reservedMargin = paddingBottom; + } else { + reservedMargin = 0; + } + cell.layoutVertically(y, reservedMargin); + } + y += rowHeights[j] + verticalSpacing; + } + } + + if (isUndefinedHeight()) { + int outerHeight = y - verticalSpacing + + layoutManager.getPaddingBottom(element) + + layoutManager.getBorderHeight(element); + element.getStyle().setHeight(outerHeight, Unit.PX); + getConnector().getLayoutManager().reportOuterHeight(getConnector(), + outerHeight); + } + } + + void layoutCellsHorizontally() { + LayoutManager layoutManager = LayoutManager.get(client); + Element element = getElement(); + int x = layoutManager.getPaddingLeft(element); + int paddingRight = layoutManager.getPaddingRight(element); + int horizontalSpacing = getHorizontalSpacing(); + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + int reservedMargin; + // Make room for layout padding for cells reaching the + // right edge of the layout + if (i + cell.colspan >= cells.length) { + reservedMargin = paddingRight; + } else { + reservedMargin = 0; + } + cell.layoutHorizontally(x, reservedMargin); + } + } + x += columnWidths[i] + horizontalSpacing; + } + + if (isUndefinedWidth()) { + int outerWidth = x - horizontalSpacing + + layoutManager.getPaddingRight(element) + + layoutManager.getBorderWidth(element); + element.getStyle().setWidth(outerWidth, Unit.PX); + getConnector().getLayoutManager().reportOuterWidth(getConnector(), + outerWidth); + } + } + + private boolean isUndefinedHeight() { + return getConnector().isUndefinedHeight(); + } + + private boolean isUndefinedWidth() { + return getConnector().isUndefinedWidth(); + } + + private void detectRowHeights() { + for (int i = 0; i < rowHeights.length; i++) { + rowHeights[i] = 0; + } + + // collect min rowheight from non-rowspanned cells + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + if (cell.rowspan == 1) { + if (!cell.hasRelativeHeight() + && rowHeights[j] < cell.getHeight()) { + rowHeights[j] = cell.getHeight(); + } + } else { + storeRowSpannedCell(cell); + } + } + } + } + + distributeRowSpanHeights(); + + minRowHeights = cloneArray(rowHeights); + } + + private void detectColWidths() { + // collect min colwidths from non-colspanned cells + for (int i = 0; i < columnWidths.length; i++) { + columnWidths[i] = 0; + } + + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + if (cell.colspan == 1) { + if (!cell.hasRelativeWidth() + && columnWidths[i] < cell.getWidth()) { + columnWidths[i] = cell.getWidth(); + } + } else { + storeColSpannedCell(cell); + } + } + } + } + + distributeColSpanWidths(); + + minColumnWidths = cloneArray(columnWidths); + } + + private void storeRowSpannedCell(Cell cell) { + SpanList l = null; + for (SpanList list : rowSpans) { + if (list.span < cell.rowspan) { + continue; + } else { + // insert before this + l = list; + break; + } + } + if (l == null) { + l = new SpanList(cell.rowspan); + rowSpans.add(l); + } else if (l.span != cell.rowspan) { + SpanList newL = new SpanList(cell.rowspan); + rowSpans.add(rowSpans.indexOf(l), newL); + l = newL; + } + l.cells.add(cell); + } + + /** + * Iterates colspanned cells, ensures cols have enough space to accommodate + * them + */ + void distributeColSpanWidths() { + for (SpanList list : colSpans) { + for (Cell cell : list.cells) { + // cells with relative content may return non 0 here if on + // subsequent renders + int width = cell.hasRelativeWidth() ? 0 : cell.getWidth(); + distributeSpanSize(columnWidths, cell.col, cell.colspan, + getHorizontalSpacing(), width, colExpandRatioArray); + } + } + } + + /** + * Iterates rowspanned cells, ensures rows have enough space to accommodate + * them + */ + private void distributeRowSpanHeights() { + for (SpanList list : rowSpans) { + for (Cell cell : list.cells) { + // cells with relative content may return non 0 here if on + // subsequent renders + int height = cell.hasRelativeHeight() ? 0 : cell.getHeight(); + distributeSpanSize(rowHeights, cell.row, cell.rowspan, + getVerticalSpacing(), height, rowExpandRatioArray); + } + } + } + + private static void distributeSpanSize(int[] dimensions, + int spanStartIndex, int spanSize, int spacingSize, int size, + int[] expansionRatios) { + int allocated = dimensions[spanStartIndex]; + for (int i = 1; i < spanSize; i++) { + allocated += spacingSize + dimensions[spanStartIndex + i]; + } + if (allocated < size) { + // dimensions needs to be expanded due spanned cell + int neededExtraSpace = size - allocated; + int allocatedExtraSpace = 0; + + // Divide space according to expansion ratios if any span has a + // ratio + int totalExpansion = 0; + for (int i = 0; i < spanSize; i++) { + int itemIndex = spanStartIndex + i; + totalExpansion += expansionRatios[itemIndex]; + } + + for (int i = 0; i < spanSize; i++) { + int itemIndex = spanStartIndex + i; + int expansion; + if (totalExpansion == 0) { + // Divide equally among all cells if there are no + // expansion ratios + expansion = neededExtraSpace / spanSize; + } else { + expansion = neededExtraSpace * expansionRatios[itemIndex] + / totalExpansion; + } + dimensions[itemIndex] += expansion; + allocatedExtraSpace += expansion; + } + + // We might still miss a couple of pixels because of + // rounding errors... + if (neededExtraSpace > allocatedExtraSpace) { + for (int i = 0; i < spanSize; i++) { + // Add one pixel to every cell until we have + // compensated for any rounding error + int itemIndex = spanStartIndex + i; + dimensions[itemIndex] += 1; + allocatedExtraSpace += 1; + if (neededExtraSpace == allocatedExtraSpace) { + break; + } + } + } + } + } + + private LinkedList<SpanList> colSpans = new LinkedList<SpanList>(); + private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>(); + + private class SpanList { + final int span; + List<Cell> cells = new LinkedList<Cell>(); + + public SpanList(int span) { + this.span = span; + } + } + + void storeColSpannedCell(Cell cell) { + SpanList l = null; + for (SpanList list : colSpans) { + if (list.span < cell.colspan) { + continue; + } else { + // insert before this + l = list; + break; + } + } + if (l == null) { + l = new SpanList(cell.colspan); + colSpans.add(l); + } else if (l.span != cell.colspan) { + + SpanList newL = new SpanList(cell.colspan); + colSpans.add(colSpans.indexOf(l), newL); + l = newL; + } + l.cells.add(cell); + } + + Cell[][] cells; + + /** + * Private helper class. + */ + class Cell { + public Cell(int row, int col) { + this.row = row; + this.col = col; + } + + public boolean hasContent() { + return hasContent; + } + + public boolean hasRelativeHeight() { + if (slot != null) { + return slot.getChild().isRelativeHeight(); + } else { + return true; + } + } + + /** + * @return total of spanned cols + */ + private int getAvailableWidth() { + int width = columnWidths[col]; + for (int i = 1; i < colspan; i++) { + width += getHorizontalSpacing() + columnWidths[col + i]; + } + return width; + } + + /** + * @return total of spanned rows + */ + private int getAvailableHeight() { + int height = rowHeights[row]; + for (int i = 1; i < rowspan; i++) { + height += getVerticalSpacing() + rowHeights[row + i]; + } + return height; + } + + public void layoutHorizontally(int x, int marginRight) { + if (slot != null) { + slot.positionHorizontally(x, getAvailableWidth(), marginRight); + } + } + + public void layoutVertically(int y, int marginBottom) { + if (slot != null) { + slot.positionVertically(y, getAvailableHeight(), marginBottom); + } + } + + public int getWidth() { + if (slot != null) { + return slot.getUsedWidth(); + } else { + return 0; + } + } + + public int getHeight() { + if (slot != null) { + return slot.getUsedHeight(); + } else { + return 0; + } + } + + protected boolean hasRelativeWidth() { + if (slot != null) { + return slot.getChild().isRelativeWidth(); + } else { + return true; + } + } + + final int row; + final int col; + int colspan = 1; + int rowspan = 1; + + private boolean hasContent; + + private AlignmentInfo alignment; + + ComponentConnectorLayoutSlot slot; + + public void updateFromUidl(UIDL cellUidl) { + // Set cell width + colspan = cellUidl.hasAttribute("w") ? cellUidl + .getIntAttribute("w") : 1; + // Set cell height + rowspan = cellUidl.hasAttribute("h") ? cellUidl + .getIntAttribute("h") : 1; + // ensure we will lose reference to old cells, now overlapped by + // this cell + for (int i = 0; i < colspan; i++) { + for (int j = 0; j < rowspan; j++) { + if (i > 0 || j > 0) { + cells[col + i][row + j] = null; + } + } + } + + UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested + // about childUidl + hasContent = childUidl != null; + if (hasContent) { + ComponentConnector childConnector = client + .getPaintable(childUidl); + + if (slot == null || slot.getChild() != childConnector) { + slot = new ComponentConnectorLayoutSlot(CLASSNAME, + childConnector, getConnector()); + if (childConnector.isRelativeWidth()) { + slot.getWrapperElement().getStyle() + .setWidth(100, Unit.PCT); + } + Element slotWrapper = slot.getWrapperElement(); + getElement().appendChild(slotWrapper); + + Widget widget = childConnector.getWidget(); + insert(widget, slotWrapper, getWidgetCount(), false); + Cell oldCell = widgetToCell.put(widget, this); + if (oldCell != null) { + oldCell.slot.getWrapperElement().removeFromParent(); + oldCell.slot = null; + } + } + + } + } + + public void setAlignment(AlignmentInfo alignmentInfo) { + slot.setAlignment(alignmentInfo); + } + } + + Cell getCell(int row, int col) { + return cells[col][row]; + } + + /** + * Creates a new Cell with the given coordinates. If an existing cell was + * found, returns that one. + * + * @param row + * @param col + * @return + */ + Cell createCell(int row, int col) { + Cell cell = getCell(row, col); + if (cell == null) { + cell = new Cell(row, col); + cells[col][row] = cell; + } + return cell; + } + + /** + * Returns the deepest nested child component which contains "element". The + * child component is also returned if "element" is part of its caption. + * + * @param element + * An element that is a nested sub element of the root element in + * this layout + * @return The Paintable which the element is a part of. Null if the element + * belongs to the layout and not to a child. + */ + ComponentConnector getComponent(Element element) { + return Util.getConnectorForElement(client, this, element); + } + + void setCaption(Widget widget, VCaption caption) { + VLayoutSlot slot = widgetToCell.get(widget).slot; + + if (caption != null) { + // Logical attach. + getChildren().add(caption); + } + + // Physical attach if not null, also removes old caption + slot.setCaption(caption); + + if (caption != null) { + // Adopt. + adopt(caption); + } + } + + private void togglePrefixedStyleName(String name, boolean enabled) { + if (enabled) { + addStyleDependentName(name); + } else { + removeStyleDependentName(name); + } + } + + void updateMarginStyleNames(VMarginInfo marginInfo) { + togglePrefixedStyleName("margin-top", marginInfo.hasTop()); + togglePrefixedStyleName("margin-right", marginInfo.hasRight()); + togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); + togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); + } + + void updateSpacingStyleName(boolean spacingEnabled) { + String styleName = getStylePrimaryName(); + if (spacingEnabled) { + spacingMeasureElement.addClassName(styleName + "-spacing-on"); + spacingMeasureElement.removeClassName(styleName + "-spacing-off"); + } else { + spacingMeasureElement.removeClassName(styleName + "-spacing-on"); + spacingMeasureElement.addClassName(styleName + "-spacing-off"); + } + } + + public void setSize(int rows, int cols) { + if (cells == null) { + cells = new Cell[cols][rows]; + } else if (cells.length != cols || cells[0].length != rows) { + Cell[][] newCells = new Cell[cols][rows]; + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + if (i < cols && j < rows) { + newCells[i][j] = cells[i][j]; + } + } + } + cells = newCells; + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/label/LabelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/label/LabelConnector.java new file mode 100644 index 0000000000..308a4860b5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/label/LabelConnector.java @@ -0,0 +1,74 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.label; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.PreElement; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.Label; + +@Connect(value = Label.class, loadStyle = LoadStyle.EAGER) +public class LabelConnector extends AbstractComponentConnector implements + Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().setConnection(client); + if (!isRealUpdate(uidl)) { + return; + } + + boolean sinkOnloads = false; + + final String mode = uidl.getStringAttribute("mode"); + if (mode == null || "text".equals(mode)) { + getWidget().setText(uidl.getChildString(0)); + } else if ("pre".equals(mode)) { + PreElement preElement = Document.get().createPreElement(); + preElement.setInnerText(uidl.getChildUIDL(0).getChildString(0)); + // clear existing content + getWidget().setHTML(""); + // add preformatted text to dom + getWidget().getElement().appendChild(preElement); + } else if ("uidl".equals(mode)) { + getWidget().setHTML(uidl.getChildrenAsXML()); + } else if ("xhtml".equals(mode)) { + UIDL content = uidl.getChildUIDL(0).getChildUIDL(0); + if (content.getChildCount() > 0) { + getWidget().setHTML(content.getChildString(0)); + } else { + getWidget().setHTML(""); + } + sinkOnloads = true; + } else if ("xml".equals(mode)) { + getWidget().setHTML(uidl.getChildUIDL(0).getChildString(0)); + } else if ("raw".equals(mode)) { + getWidget().setHTML(uidl.getChildUIDL(0).getChildString(0)); + sinkOnloads = true; + } else { + getWidget().setText(""); + } + if (sinkOnloads) { + Util.sinkOnloadForImages(getWidget().getElement()); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VLabel.class); + } + + @Override + public VLabel getWidget() { + return (VLabel) super.getWidget(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/label/VLabel.java b/src/com/vaadin/terminal/gwt/client/ui/label/VLabel.java new file mode 100644 index 0000000000..f47b8437b7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/label/VLabel.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.label; + +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; + +public class VLabel extends HTML { + + public static final String CLASSNAME = "v-label"; + private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w"; + + private ApplicationConnection connection; + + public VLabel() { + super(); + setStyleName(CLASSNAME); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + public VLabel(String text) { + super(text); + setStyleName(CLASSNAME); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + event.stopPropagation(); + return; + } + if (connection != null) { + connection.handleTooltipEvent(event, this); + } + } + + @Override + public void setWidth(String width) { + super.setWidth(width); + if (width == null || width.equals("")) { + setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true); + getElement().getStyle().setDisplay(Display.INLINE_BLOCK); + } else { + setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false); + getElement().getStyle().clearDisplay(); + } + } + + @Override + public void setText(String text) { + if (BrowserInfo.get().isIE8()) { + // #3983 - IE8 incorrectly replaces \n with <br> so we do the + // escaping manually and set as HTML + super.setHTML(Util.escapeHTML(text)); + } else { + super.setText(text); + } + } + + void setConnection(ApplicationConnection client) { + connection = client; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayout.java deleted file mode 100644 index 2ca58a38c2..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayout.java +++ /dev/null @@ -1,397 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui.layout; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VMarginInfo; - -public abstract class CellBasedLayout extends ComplexPanel implements Container { - - protected Map<Widget, ChildComponentContainer> widgetToComponentContainer = new HashMap<Widget, ChildComponentContainer>(); - - protected ApplicationConnection client = null; - - protected DivElement root; - - public static final int ORIENTATION_VERTICAL = 0; - public static final int ORIENTATION_HORIZONTAL = 1; - - protected Margins activeMargins = new Margins(0, 0, 0, 0); - protected VMarginInfo activeMarginsInfo = new VMarginInfo(-1); - - protected boolean spacingEnabled = false; - protected final Spacing spacingFromCSS = new Spacing(12, 12); - protected final Spacing activeSpacing = new Spacing(0, 0); - - private boolean dynamicWidth; - - private boolean dynamicHeight; - - private final DivElement clearElement = Document.get().createDivElement(); - - private String lastStyleName = ""; - - private boolean marginsNeedsRecalculation = false; - - protected String STYLENAME_SPACING = ""; - protected String STYLENAME_MARGIN_TOP = ""; - protected String STYLENAME_MARGIN_RIGHT = ""; - protected String STYLENAME_MARGIN_BOTTOM = ""; - protected String STYLENAME_MARGIN_LEFT = ""; - - public static class Spacing { - - public int hSpacing = 0; - public int vSpacing = 0; - - public Spacing(int hSpacing, int vSpacing) { - this.hSpacing = hSpacing; - this.vSpacing = vSpacing; - } - - @Override - public String toString() { - return "Spacing [hSpacing=" + hSpacing + ",vSpacing=" + vSpacing - + "]"; - } - - } - - public CellBasedLayout() { - super(); - - setElement(Document.get().createDivElement()); - getElement().getStyle().setProperty("overflow", "hidden"); - if (BrowserInfo.get().isIE()) { - getElement().getStyle().setProperty("position", "relative"); - getElement().getStyle().setProperty("zoom", "1"); - } - - root = Document.get().createDivElement(); - root.getStyle().setProperty("overflow", "hidden"); - if (BrowserInfo.get().isIE()) { - root.getStyle().setProperty("position", "relative"); - } - - getElement().appendChild(root); - - Style style = clearElement.getStyle(); - style.setProperty("width", "0px"); - style.setProperty("height", "0px"); - style.setProperty("clear", "both"); - style.setProperty("overflow", "hidden"); - root.appendChild(clearElement); - - } - - public boolean hasChildComponent(Widget component) { - return widgetToComponentContainer.containsKey(component); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - - // Only non-cached UIDL:s can introduce changes - if (uidl.getBooleanAttribute("cached")) { - return; - } - - /** - * Margin and spacind detection depends on classNames and must be set - * before setting size. Here just update the details from UIDL and from - * overridden setStyleName run actual margin detections. - */ - updateMarginAndSpacingInfo(uidl); - - /* - * This call should be made first. Ensure correct implementation, handle - * size etc. - */ - if (client.updateComponent(this, uidl, true)) { - return; - } - - handleDynamicDimensions(uidl); - } - - @Override - public void setStyleName(String styleName) { - super.setStyleName(styleName); - - if (isAttached() && marginsNeedsRecalculation - || !lastStyleName.equals(styleName)) { - measureMarginsAndSpacing(); - lastStyleName = styleName; - marginsNeedsRecalculation = false; - } - - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - - /* - * Ensure the the dynamic width stays up to date even if size is altered - * only on client side. - */ - if (width == null || width.equals("")) { - dynamicWidth = true; - } else { - dynamicWidth = false; - } - } - - private void handleDynamicDimensions(UIDL uidl) { - String w = uidl.hasAttribute("width") ? uidl - .getStringAttribute("width") : ""; - - String h = uidl.hasAttribute("height") ? uidl - .getStringAttribute("height") : ""; - - if (w.equals("")) { - dynamicWidth = true; - } else { - dynamicWidth = false; - } - - if (h.equals("")) { - dynamicHeight = true; - } else { - dynamicHeight = false; - } - - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - - /* - * Ensure the the dynamic height stays up to date even if size is - * altered only on client side. - */ - if (height == null || height.equals("")) { - dynamicHeight = true; - } else { - dynamicHeight = false; - } - } - - protected void addOrMoveChild(ChildComponentContainer childComponent, - int position) { - if (childComponent.getParent() == this) { - if (getWidgetIndex(childComponent) != position) { - // Detach from old position child. - childComponent.removeFromParent(); - - // Logical attach. - getChildren().insert(childComponent, position); - - root.insertBefore(childComponent.getElement(), root - .getChildNodes().getItem(position)); - - adopt(childComponent); - } - } else { - widgetToComponentContainer.put(childComponent.getWidget(), - childComponent); - - // Logical attach. - getChildren().insert(childComponent, position); - - // avoid inserts (they are slower than appends) - boolean insert = true; - if (widgetToComponentContainer.size() == position) { - insert = false; - } - if (insert) { - root.insertBefore(childComponent.getElement(), root - .getChildNodes().getItem(position)); - } else { - root.insertBefore(childComponent.getElement(), clearElement); - } - // Adopt. - adopt(childComponent); - - } - - } - - protected ChildComponentContainer getComponentContainer(Widget child) { - return widgetToComponentContainer.get(child); - } - - protected boolean isDynamicWidth() { - return dynamicWidth; - } - - protected boolean isDynamicHeight() { - return dynamicHeight; - } - - private void updateMarginAndSpacingInfo(UIDL uidl) { - if (!uidl.hasAttribute("invisible")) { - int bitMask = uidl.getIntAttribute("margins"); - if (activeMarginsInfo.getBitMask() != bitMask) { - activeMarginsInfo = new VMarginInfo(bitMask); - marginsNeedsRecalculation = true; - } - boolean spacing = uidl.getBooleanAttribute("spacing"); - if (spacing != spacingEnabled) { - marginsNeedsRecalculation = true; - spacingEnabled = spacing; - } - } - } - - private static DivElement measurement; - private static DivElement measurement2; - private static DivElement measurement3; - private static DivElement helper; - - static { - helper = Document.get().createDivElement(); - helper.setInnerHTML("<div style=\"position:absolute;top:0;left:0;height:0;visibility:hidden;overflow:hidden;\">" - + "<div style=\"width:0;height:0;visibility:hidden;overflow:hidden;\">" - + "</div></div>" - + "<div style=\"position:absolute;height:0;overflow:hidden;\"></div>"); - NodeList<Node> childNodes = helper.getChildNodes(); - measurement = (DivElement) childNodes.getItem(0); - measurement2 = (DivElement) measurement.getFirstChildElement(); - measurement3 = (DivElement) childNodes.getItem(1); - } - - protected boolean measureMarginsAndSpacing() { - if (!isAttached()) { - return false; - } - - // Measure spacing (actually CSS padding) - measurement3.setClassName(STYLENAME_SPACING - + (spacingEnabled ? "-on" : "-off")); - - String sn = getStylePrimaryName() + "-margin"; - - if (activeMarginsInfo.hasTop()) { - sn += " " + STYLENAME_MARGIN_TOP; - } - if (activeMarginsInfo.hasBottom()) { - sn += " " + STYLENAME_MARGIN_BOTTOM; - } - if (activeMarginsInfo.hasLeft()) { - sn += " " + STYLENAME_MARGIN_LEFT; - } - if (activeMarginsInfo.hasRight()) { - sn += " " + STYLENAME_MARGIN_RIGHT; - } - - // Measure top and left margins (actually CSS padding) - measurement.setClassName(sn); - - root.appendChild(helper); - - activeSpacing.vSpacing = measurement3.getOffsetHeight(); - activeSpacing.hSpacing = measurement3.getOffsetWidth(); - - activeMargins.setMarginTop(measurement2.getOffsetTop()); - activeMargins.setMarginLeft(measurement2.getOffsetLeft()); - activeMargins.setMarginRight(measurement.getOffsetWidth() - - activeMargins.getMarginLeft()); - activeMargins.setMarginBottom(measurement.getOffsetHeight() - - activeMargins.getMarginTop()); - - // ApplicationConnection.getConsole().log("Margins: " + activeMargins); - // ApplicationConnection.getConsole().log("Spacing: " + activeSpacing); - // Util.alert("Margins: " + activeMargins); - root.removeChild(helper); - - // apply margin - Style style = root.getStyle(); - style.setPropertyPx("marginLeft", activeMargins.getMarginLeft()); - style.setPropertyPx("marginRight", activeMargins.getMarginRight()); - style.setPropertyPx("marginTop", activeMargins.getMarginTop()); - style.setPropertyPx("marginBottom", activeMargins.getMarginBottom()); - - return true; - } - - protected ChildComponentContainer getFirstChildComponentContainer() { - int size = getChildren().size(); - if (size < 1) { - return null; - } - - return (ChildComponentContainer) getChildren().get(0); - } - - protected void removeChildrenAfter(int pos) { - // Remove all children after position "pos" but leave the clear element - // in place - - int toRemove = getChildren().size() - pos; - while (toRemove-- > 0) { - /* flag to not if widget has been moved and rendered elsewhere */ - boolean relocated = false; - ChildComponentContainer child = (ChildComponentContainer) getChildren() - .get(pos); - Widget widget = child.getWidget(); - if (widget == null) { - // a rare case where child component has been relocated and - // rendered elsewhere - // clean widgetToComponentContainer map by iterating the correct - // mapping - Iterator<Widget> iterator = widgetToComponentContainer.keySet() - .iterator(); - while (iterator.hasNext()) { - Widget key = iterator.next(); - if (widgetToComponentContainer.get(key) == child) { - widget = key; - relocated = true; - break; - } - } - if (widget == null) { - throw new NullPointerException(); - } - } - // ChildComponentContainer remove = - widgetToComponentContainer.remove(widget); - remove(child); - if (!relocated) { - Paintable p = (Paintable) widget; - client.unregisterPaintable(p); - } - } - - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - ChildComponentContainer componentContainer = widgetToComponentContainer - .remove(oldComponent); - if (componentContainer == null) { - return; - } - - componentContainer.setWidget(newComponent); - client.unregisterPaintable((Paintable) oldComponent); - widgetToComponentContainer.put(newComponent, componentContainer); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/ChildComponentContainer.java b/src/com/vaadin/terminal/gwt/client/ui/layout/ChildComponentContainer.java deleted file mode 100644 index 36d18306fa..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/layout/ChildComponentContainer.java +++ /dev/null @@ -1,794 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui.layout; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.BorderStyle; -import com.google.gwt.dom.client.TableElement; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; -import com.vaadin.terminal.gwt.client.RenderInformation.Size; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; - -public class ChildComponentContainer extends Panel { - - /** - * Size of the container DIV excluding any margins and also excluding the - * expansion amount (containerExpansion) - */ - private Size contSize = new Size(0, 0); - - /** - * Size of the widget inside the container DIV - */ - private Size widgetSize = new Size(0, 0); - /** - * Size of the caption - */ - private int captionRequiredWidth = 0; - private int captionWidth = 0; - private int captionHeight = 0; - - /** - * - * Padding added to the container when it is larger than the component. - */ - private Size containerExpansion = new Size(0, 0); - - private double expandRatio; - - // private int containerMarginLeft = 0; - private int containerMarginTop = 0; - - AlignmentInfo alignment = AlignmentInfo.TOP_LEFT; - - private int alignmentLeftOffsetForWidget = 0; - private int alignmentLeftOffsetForCaption = 0; - /** - * Top offset for implementing alignment. Top offset is set to the container - * DIV as it otherwise would have to be set to either the Caption or the - * Widget depending on whether there is a caption and where the caption is - * located. - */ - private int alignmentTopOffset = 0; - - // private Margins alignmentOffset = new Margins(0, 0, 0, 0); - private VCaption caption = null; - private DivElement containerDIV; - private DivElement widgetDIV; - private Widget widget; - private FloatSize relativeSize = null; - - public ChildComponentContainer(Widget widget, int orientation) { - super(); - - containerDIV = Document.get().createDivElement(); - - widgetDIV = Document.get().createDivElement(); - if (BrowserInfo.get().isFF2()) { - // Style style = widgetDIV.getStyle(); - // FF2 chokes on some floats very easily. Measuring size escpecially - // becomes terribly slow - TableElement tableEl = Document.get().createTableElement(); - tableEl.setInnerHTML("<tbody><tr><td><div></div></td></tr></tbody>"); - DivElement div = (DivElement) tableEl.getFirstChildElement() - .getFirstChildElement().getFirstChildElement() - .getFirstChildElement(); - tableEl.setCellPadding(0); - tableEl.setCellSpacing(0); - tableEl.setBorder(0); - div.getStyle().setProperty("padding", "0"); - - setElement(tableEl); - containerDIV = div; - } else { - setFloat(widgetDIV, "left"); - setElement(containerDIV); - containerDIV.getStyle().setProperty("height", "0"); - containerDIV.getStyle().setProperty("width", "0px"); - containerDIV.getStyle().setProperty("overflow", "hidden"); - } - - if (BrowserInfo.get().isIE()) { - /* - * IE requires position: relative on overflow:hidden elements if - * they should hide position:relative elements. Without this e.g. a - * 1000x1000 Panel inside an 500x500 OrderedLayout will not be - * clipped but fully shown. - */ - containerDIV.getStyle().setProperty("position", "relative"); - widgetDIV.getStyle().setProperty("position", "relative"); - } - - containerDIV.appendChild(widgetDIV); - - setOrientation(orientation); - - setWidget(widget); - - } - - public void setWidget(Widget w) { - // Validate - if (w == widget) { - return; - } - - // Detach new child. - if (w != null) { - w.removeFromParent(); - } - - // Remove old child. - if (widget != null) { - remove(widget); - } - - // Logical attach. - widget = w; - - if (w != null) { - // Physical attach. - widgetDIV.appendChild(widget.getElement()); - adopt(w); - } - } - - private static void setFloat(Element div, String floatString) { - if (BrowserInfo.get().isIE()) { - div.getStyle().setProperty("styleFloat", floatString); - // IE requires display:inline for margin-left to work together - // with float:left - if (floatString.equals("left")) { - div.getStyle().setProperty("display", "inline"); - } else { - div.getStyle().setProperty("display", "block"); - } - - } else { - div.getStyle().setProperty("cssFloat", floatString); - } - } - - public void setOrientation(int orientation) { - if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) { - setFloat(getElement(), "left"); - } else { - setFloat(getElement(), ""); - } - setHeight("0px"); - // setWidth("0px"); - contSize.setHeight(0); - contSize.setWidth(0); - // containerMarginLeft = 0; - containerMarginTop = 0; - containerDIV.getStyle().setProperty("paddingLeft", "0"); - containerDIV.getStyle().setProperty("paddingTop", "0"); - - containerExpansion.setHeight(0); - containerExpansion.setWidth(0); - - // Clear old alignments - clearAlignments(); - - } - - public void renderChild(UIDL childUIDL, ApplicationConnection client, - int fixedWidth) { - /* - * Must remove width specification from container before rendering to - * allow components to grow in horizontal direction. - * - * For fixed width layouts we specify the width directly so that height - * is automatically calculated correctly (e.g. for Labels). - */ - /* - * This should no longer be needed (after #2563) as all components are - * such that they can be rendered inside a 0x0 DIV. - * - * The exception seems to be complex components (Tree and Table) on - * Opera (#3444). - */ - if (fixedWidth < 0 && BrowserInfo.get().isOpera()) { - setUnlimitedContainerWidth(); - } - ((Paintable) widget).updateFromUIDL(childUIDL, client); - } - - public void setUnlimitedContainerWidth() { - setLimitedContainerWidth(1000000); - } - - public void setLimitedContainerWidth(int width) { - containerDIV.getStyle().setProperty("width", width + "px"); - } - - public void updateWidgetSize() { - /* - * Widget wrapper includes margin which the widget offsetWidth/Height - * does not include - */ - int w = Util.getRequiredWidth(widgetDIV); - - // IE7 ignores the width of the content if there's a border (#3915) - if (BrowserInfo.get().isIE7()) { - // Also read the inner width of the target element - int clientWidth = widget.getElement().getClientWidth(); - - // If the widths are different, there might be a border involved and - // then the width should be calculated without borders - if (w != clientWidth) { - // Remember old border style and remove current border - Style style = widget.getElement().getStyle(); - String oldBorderStyle = style.getBorderStyle(); - style.setBorderStyle(BorderStyle.NONE); - - // Calculate width without borders - int newWidth = Util.getRequiredWidth(widgetDIV); - - // Restore old border style - style.setProperty("borderStyle", oldBorderStyle); - - // Borders triggered the bug if the element is wider without - // borders - if (newWidth > w) { - // Use new measured width + the border width calculated as - // the difference between previous inner and outer widths - w = newWidth + (w - clientWidth); - } - } - } - - int h = Util.getRequiredHeight(widgetDIV); - - widgetSize.setWidth(w); - widgetSize.setHeight(h); - - // ApplicationConnection.getConsole().log( - // Util.getSimpleName(widget) + " size is " + w + "," + h); - - } - - public void setMarginLeft(int marginLeft) { - // containerMarginLeft = marginLeft; - containerDIV.getStyle().setPropertyPx("paddingLeft", marginLeft); - } - - public void setMarginTop(int marginTop) { - containerMarginTop = marginTop; - containerDIV.getStyle().setPropertyPx("paddingTop", - marginTop + alignmentTopOffset); - - updateContainerDOMSize(); - } - - public void updateAlignments(int parentWidth, int parentHeight) { - if (parentHeight == -1) { - parentHeight = contSize.getHeight(); - } - if (parentWidth == -1) { - parentWidth = contSize.getWidth(); - } - - alignmentTopOffset = calculateVerticalAlignmentTopOffset(parentHeight); - - calculateHorizontalAlignment(parentWidth); - - applyAlignments(); - - } - - private void applyAlignments() { - - // Update top margin to take alignment into account - setMarginTop(containerMarginTop); - - if (caption != null) { - caption.getElement().getStyle() - .setPropertyPx("marginLeft", alignmentLeftOffsetForCaption); - } - widgetDIV.getStyle().setPropertyPx("marginLeft", - alignmentLeftOffsetForWidget); - } - - public int getCaptionRequiredWidth() { - if (caption == null) { - return 0; - } - - return captionRequiredWidth; - } - - public int getCaptionWidth() { - if (caption == null) { - return 0; - } - - return captionWidth; - } - - public int getCaptionHeight() { - if (caption == null) { - return 0; - } - - return captionHeight; - } - - public int getCaptionWidthAfterComponent() { - if (caption == null || !caption.shouldBePlacedAfterComponent()) { - return 0; - } - - return getCaptionWidth(); - } - - public int getCaptionHeightAboveComponent() { - if (caption == null || caption.shouldBePlacedAfterComponent()) { - return 0; - } - - return getCaptionHeight(); - } - - private int calculateVerticalAlignmentTopOffset(int emptySpace) { - if (alignment.isTop()) { - return 0; - } - - if (caption != null) { - if (caption.shouldBePlacedAfterComponent()) { - /* - * Take into account the rare case that the caption on the right - * side of the component AND is higher than the component - */ - emptySpace -= Math.max(widgetSize.getHeight(), - caption.getHeight()); - } else { - emptySpace -= widgetSize.getHeight(); - emptySpace -= getCaptionHeight(); - } - } else { - /* - * There is no caption and thus we do not need to take anything but - * the widget into account - */ - emptySpace -= widgetSize.getHeight(); - } - - int top = 0; - if (alignment.isVerticalCenter()) { - top = emptySpace / 2; - } else if (alignment.isBottom()) { - top = emptySpace; - } - - if (top < 0) { - top = 0; - } - return top; - } - - private void calculateHorizontalAlignment(int emptySpace) { - alignmentLeftOffsetForCaption = 0; - alignmentLeftOffsetForWidget = 0; - - if (alignment.isLeft()) { - return; - } - - int captionSpace = emptySpace; - int widgetSpace = emptySpace; - - if (caption != null) { - // There is a caption - if (caption.shouldBePlacedAfterComponent()) { - /* - * The caption is after component. In this case the caption - * needs no alignment. - */ - captionSpace = 0; - widgetSpace -= widgetSize.getWidth(); - widgetSpace -= getCaptionWidth(); - } else { - /* - * The caption is above the component. Caption and widget needs - * separate alignment offsets. - */ - widgetSpace -= widgetSize.getWidth(); - captionSpace -= getCaptionWidth(); - } - } else { - /* - * There is no caption and thus we do not need to take anything but - * the widget into account - */ - captionSpace = 0; - widgetSpace -= widgetSize.getWidth(); - } - - if (alignment.isHorizontalCenter()) { - alignmentLeftOffsetForCaption = captionSpace / 2; - alignmentLeftOffsetForWidget = widgetSpace / 2; - } else if (alignment.isRight()) { - alignmentLeftOffsetForCaption = captionSpace; - alignmentLeftOffsetForWidget = widgetSpace; - } - - if (alignmentLeftOffsetForCaption < 0) { - alignmentLeftOffsetForCaption = 0; - } - if (alignmentLeftOffsetForWidget < 0) { - alignmentLeftOffsetForWidget = 0; - } - - } - - public void setAlignment(AlignmentInfo alignmentInfo) { - alignment = alignmentInfo; - } - - public Size getWidgetSize() { - return widgetSize; - } - - public void updateCaption(UIDL uidl, ApplicationConnection client) { - if (VCaption.isNeeded(uidl)) { - // We need a caption - - VCaption newCaption = caption; - - if (newCaption == null) { - newCaption = new VCaption((Paintable) widget, client); - // Set initial height to avoid Safari flicker - newCaption.setHeight("18px"); - // newCaption.setHeight(newCaption.getHeight()); // This might - // be better... ?? - if (BrowserInfo.get().isIE()) { - /* - * Must attach caption here so IE sends an immediate onload - * event for images coming from the cache - */ - setCaption(newCaption); - } - } - - boolean positionChanged = newCaption.updateCaption(uidl); - - if (newCaption != caption || positionChanged) { - setCaption(newCaption); - } - - } else { - // Caption is not needed - if (caption != null) { - remove(caption); - } - - } - - updateCaptionSize(); - - if (relativeSize == null) { - /* - * relativeSize may be null if component is updated via independent - * update, after it has initially been hidden. See #4608 - * - * It might also change in which case there would be similar issues. - * - * Yes, it is an ugly hack. Don't come telling me about it. - */ - setRelativeSize(Util.parseRelativeSize(uidl)); - } - } - - public void updateCaptionSize() { - captionWidth = 0; - captionHeight = 0; - - if (caption != null) { - captionWidth = caption.getRenderedWidth(); - captionHeight = caption.getHeight(); - captionRequiredWidth = caption.getRequiredWidth(); - - /* - * ApplicationConnection.getConsole().log( - * "Caption rendered width: " + captionWidth + - * ", caption required width: " + captionRequiredWidth + - * ", caption height: " + captionHeight); - */ - } - - } - - private void setCaption(VCaption newCaption) { - // Validate - // if (newCaption == caption) { - // return; - // } - - // Detach new child. - if (newCaption != null) { - newCaption.removeFromParent(); - } - - // Remove old child. - if (caption != null && newCaption != caption) { - remove(caption); - } - - // Logical attach. - caption = newCaption; - - if (caption != null) { - // Physical attach. - if (caption.shouldBePlacedAfterComponent()) { - Util.setFloat(caption.getElement(), "left"); - containerDIV.appendChild(caption.getElement()); - } else { - Util.setFloat(caption.getElement(), ""); - containerDIV.insertBefore(caption.getElement(), widgetDIV); - } - - adopt(caption); - } - - } - - @Override - public boolean remove(Widget child) { - // Validate - if (child != caption && child != widget) { - return false; - } - - // Orphan - orphan(child); - - // Physical && Logical Detach - if (child == caption) { - containerDIV.removeChild(child.getElement()); - caption = null; - } else { - widgetDIV.removeChild(child.getElement()); - widget = null; - } - - return true; - } - - public Iterator<Widget> iterator() { - return new ChildComponentContainerIterator<Widget>(); - } - - public class ChildComponentContainerIterator<T> implements Iterator<Widget> { - private int id = 0; - - public boolean hasNext() { - return (id < size()); - } - - public Widget next() { - Widget w = get(id); - id++; - return w; - } - - private Widget get(int i) { - if (i == 0) { - if (widget != null) { - return widget; - } else if (caption != null) { - return caption; - } else { - throw new NoSuchElementException(); - } - } else if (i == 1) { - if (widget != null && caption != null) { - return caption; - } else { - throw new NoSuchElementException(); - } - } else { - throw new NoSuchElementException(); - } - } - - public void remove() { - int toRemove = id - 1; - if (toRemove == 0) { - if (widget != null) { - ChildComponentContainer.this.remove(widget); - } else if (caption != null) { - ChildComponentContainer.this.remove(caption); - } else { - throw new IllegalStateException(); - } - - } else if (toRemove == 1) { - if (widget != null && caption != null) { - ChildComponentContainer.this.remove(caption); - } else { - throw new IllegalStateException(); - } - } else { - throw new IllegalStateException(); - } - - id--; - } - } - - public int size() { - if (widget != null) { - if (caption != null) { - return 2; - } else { - return 1; - } - } else { - if (caption != null) { - return 1; - } else { - return 0; - } - } - } - - public Widget getWidget() { - return widget; - } - - /** - * Return true if the size of the widget has been specified in the selected - * orientation. - * - * @return - */ - public boolean widgetHasSizeSpecified(int orientation) { - String size; - if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) { - size = widget.getElement().getStyle().getProperty("width"); - } else { - size = widget.getElement().getStyle().getProperty("height"); - } - return (size != null && !size.equals("")); - } - - public boolean isComponentRelativeSized(int orientation) { - if (relativeSize == null) { - return false; - } - if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) { - return relativeSize.getWidth() >= 0; - } else { - return relativeSize.getHeight() >= 0; - } - } - - public void setRelativeSize(FloatSize relativeSize) { - this.relativeSize = relativeSize; - } - - public Size getContSize() { - return contSize; - } - - public void clearAlignments() { - alignmentLeftOffsetForCaption = 0; - alignmentLeftOffsetForWidget = 0; - alignmentTopOffset = 0; - applyAlignments(); - - } - - /** - * Sets the normalized expand ratio of this slot. The fraction that this - * slot will use of "excess space". - * - * @param expandRatio - */ - public void setNormalizedExpandRatio(double expandRatio) { - this.expandRatio = expandRatio; - } - - public int expand(int orientation, int spaceForExpansion) { - int expansionAmount = (int) (spaceForExpansion * expandRatio); - - if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) { - // HORIZONTAL - containerExpansion.setWidth(expansionAmount); - } else { - // VERTICAL - containerExpansion.setHeight(expansionAmount); - } - - return expansionAmount; - } - - public void expandExtra(int orientation, int extra) { - if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) { - // HORIZONTAL - containerExpansion.setWidth(containerExpansion.getWidth() + extra); - } else { - // VERTICAL - containerExpansion - .setHeight(containerExpansion.getHeight() + extra); - } - - } - - public void setContainerSize(int widgetAndCaptionWidth, - int widgetAndCaptionHeight) { - - int containerWidth = widgetAndCaptionWidth; - containerWidth += containerExpansion.getWidth(); - - int containerHeight = widgetAndCaptionHeight; - containerHeight += containerExpansion.getHeight(); - - // ApplicationConnection.getConsole().log( - // "Setting container size for " + Util.getSimpleName(widget) - // + " to " + containerWidth + "," + containerHeight); - - if (containerWidth < 0) { - VConsole.log("containerWidth should never be negative: " - + containerWidth); - containerWidth = 0; - } - if (containerHeight < 0) { - VConsole.log("containerHeight should never be negative: " - + containerHeight); - containerHeight = 0; - } - - contSize.setWidth(containerWidth); - contSize.setHeight(containerHeight); - - updateContainerDOMSize(); - } - - public void updateContainerDOMSize() { - int width = contSize.getWidth(); - int height = contSize.getHeight() - alignmentTopOffset; - if (width < 0) { - width = 0; - } - if (height < 0) { - height = 0; - } - - setWidth(width + "px"); - setHeight(height + "px"); - - // Also update caption max width - if (caption != null) { - if (caption.shouldBePlacedAfterComponent()) { - caption.setMaxWidth(captionWidth); - } else { - caption.setMaxWidth(width); - } - captionWidth = caption.getRenderedWidth(); - - // Remove initial height - caption.setHeight(""); - } - - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/ComponentConnectorLayoutSlot.java b/src/com/vaadin/terminal/gwt/client/ui/layout/ComponentConnectorLayoutSlot.java new file mode 100644 index 0000000000..d479e8da9d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/ComponentConnectorLayoutSlot.java @@ -0,0 +1,99 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.layout; + +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.ManagedLayout; + +public class ComponentConnectorLayoutSlot extends VLayoutSlot { + + final ComponentConnector child; + final ManagedLayout layout; + + public ComponentConnectorLayoutSlot(String baseClassName, + ComponentConnector child, ManagedLayout layout) { + super(baseClassName, child.getWidget()); + this.child = child; + this.layout = layout; + } + + public ComponentConnector getChild() { + return child; + } + + @Override + protected int getCaptionHeight() { + VCaption caption = getCaption(); + return caption != null ? getLayoutManager().getOuterHeight( + caption.getElement()) : 0; + } + + @Override + protected int getCaptionWidth() { + VCaption caption = getCaption(); + return caption != null ? getLayoutManager().getOuterWidth( + caption.getElement()) : 0; + } + + public LayoutManager getLayoutManager() { + return layout.getLayoutManager(); + } + + @Override + public void setCaption(VCaption caption) { + VCaption oldCaption = getCaption(); + if (oldCaption != null) { + getLayoutManager().unregisterDependency(layout, + oldCaption.getElement()); + } + super.setCaption(caption); + if (caption != null) { + getLayoutManager().registerDependency( + (ManagedLayout) child.getParent(), caption.getElement()); + } + } + + @Override + protected void reportActualRelativeHeight(int allocatedHeight) { + getLayoutManager().reportOuterHeight(child, allocatedHeight); + } + + @Override + protected void reportActualRelativeWidth(int allocatedWidth) { + getLayoutManager().reportOuterWidth(child, allocatedWidth); + } + + @Override + public int getWidgetHeight() { + return getLayoutManager() + .getOuterHeight(child.getWidget().getElement()); + } + + @Override + public int getWidgetWidth() { + return getLayoutManager().getOuterWidth(child.getWidget().getElement()); + } + + @Override + public boolean isUndefinedHeight() { + return child.isUndefinedHeight(); + } + + @Override + public boolean isUndefinedWidth() { + return child.isUndefinedWidth(); + } + + @Override + public boolean isRelativeHeight() { + return child.isRelativeHeight(); + } + + @Override + public boolean isRelativeWidth() { + return child.isRelativeWidth(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/ElementResizeEvent.java b/src/com/vaadin/terminal/gwt/client/ui/layout/ElementResizeEvent.java new file mode 100644 index 0000000000..a519f5db87 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/ElementResizeEvent.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.layout; + +import com.google.gwt.dom.client.Element; +import com.vaadin.terminal.gwt.client.LayoutManager; + +public class ElementResizeEvent { + private final Element element; + private final LayoutManager layoutManager; + + public ElementResizeEvent(LayoutManager layoutManager, Element element) { + this.layoutManager = layoutManager; + this.element = element; + } + + public Element getElement() { + return element; + } + + public LayoutManager getLayoutManager() { + return layoutManager; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/ElementResizeListener.java b/src/com/vaadin/terminal/gwt/client/ui/layout/ElementResizeListener.java new file mode 100644 index 0000000000..d6d3de48b8 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/ElementResizeListener.java @@ -0,0 +1,9 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.layout; + +public interface ElementResizeListener { + public void onElementResize(ElementResizeEvent e); +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/LayoutDependencyTree.java b/src/com/vaadin/terminal/gwt/client/ui/layout/LayoutDependencyTree.java new file mode 100644 index 0000000000..1ca8933ff2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/LayoutDependencyTree.java @@ -0,0 +1,521 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.layout; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.ManagedLayout; + +public class LayoutDependencyTree { + private class LayoutDependency { + private final ComponentConnector connector; + private final int direction; + + private boolean needsLayout = false; + private boolean needsMeasure = false; + + private boolean scrollingParentCached = false; + private ComponentConnector scrollingBoundary = null; + + private Set<ComponentConnector> measureBlockers = new HashSet<ComponentConnector>(); + private Set<ComponentConnector> layoutBlockers = new HashSet<ComponentConnector>(); + + public LayoutDependency(ComponentConnector connector, int direction) { + this.connector = connector; + this.direction = direction; + } + + private void addLayoutBlocker(ComponentConnector blocker) { + boolean blockerAdded = layoutBlockers.add(blocker); + if (blockerAdded && layoutBlockers.size() == 1) { + if (needsLayout) { + getLayoutQueue(direction).remove(connector); + } else { + // Propagation already done if needsLayout is set + propagatePotentialLayout(); + } + } + } + + private void removeLayoutBlocker(ComponentConnector blocker) { + boolean removed = layoutBlockers.remove(blocker); + if (removed && layoutBlockers.isEmpty()) { + if (needsLayout) { + getLayoutQueue(direction).add((ManagedLayout) connector); + } else { + propagateNoUpcomingLayout(); + } + } + } + + private void addMeasureBlocker(ComponentConnector blocker) { + boolean blockerAdded = measureBlockers.add(blocker); + if (blockerAdded && measureBlockers.size() == 1) { + if (needsMeasure) { + getMeasureQueue(direction).remove(connector); + } else { + propagatePotentialResize(); + } + } + } + + private void removeMeasureBlocker(ComponentConnector blocker) { + boolean removed = measureBlockers.remove(blocker); + if (removed && measureBlockers.isEmpty()) { + if (needsMeasure) { + getMeasureQueue(direction).add(connector); + } else { + propagateNoUpcomingResize(); + } + } + } + + public void setNeedsMeasure(boolean needsMeasure) { + if (needsMeasure && !this.needsMeasure) { + // If enabling needsMeasure + this.needsMeasure = needsMeasure; + + if (measureBlockers.isEmpty()) { + // Add to queue if there are no blockers + getMeasureQueue(direction).add(connector); + // Only need to propagate if not already propagated when + // setting blockers + propagatePotentialResize(); + } + } else if (!needsMeasure && this.needsMeasure + && measureBlockers.isEmpty()) { + // Only disable if there are no blockers (elements gets measured + // in both directions even if there is a blocker in one + // direction) + this.needsMeasure = needsMeasure; + getMeasureQueue(direction).remove(connector); + propagateNoUpcomingResize(); + } + } + + public void setNeedsLayout(boolean needsLayout) { + if (!(connector instanceof ManagedLayout)) { + throw new IllegalStateException( + "Only managed layouts can need layout, layout attempted for " + + Util.getConnectorString(connector)); + } + if (needsLayout && !this.needsLayout) { + // If enabling needsLayout + this.needsLayout = needsLayout; + + if (layoutBlockers.isEmpty()) { + // Add to queue if there are no blockers + getLayoutQueue(direction).add((ManagedLayout) connector); + // Only need to propagate if not already propagated when + // setting blockers + propagatePotentialLayout(); + } + } else if (!needsLayout && this.needsLayout + && layoutBlockers.isEmpty()) { + // Only disable if there are no layout blockers + // (SimpleManagedLayout gets layouted in both directions + // even if there is a blocker in one direction) + this.needsLayout = needsLayout; + getLayoutQueue(direction).remove(connector); + propagateNoUpcomingLayout(); + } + } + + private void propagatePotentialResize() { + for (ComponentConnector needsSize : getNeedsSizeForLayout()) { + LayoutDependency layoutDependency = getDependency(needsSize, + direction); + layoutDependency.addLayoutBlocker(connector); + } + } + + private Collection<ComponentConnector> getNeedsSizeForLayout() { + // Find all connectors that need the size of this connector for + // layouting + + // Parent needs size if it isn't relative? + // Connector itself needs size if it isn't undefined? + // Children doesn't care? + + ArrayList<ComponentConnector> needsSize = new ArrayList<ComponentConnector>(); + + if (!isUndefinedInDirection(connector, direction)) { + needsSize.add(connector); + } + if (!isRelativeInDirection(connector, direction)) { + ComponentConnector parent = connector.getParent(); + if (parent != null) { + needsSize.add(parent); + } + } + + return needsSize; + } + + private void propagateNoUpcomingResize() { + for (ComponentConnector mightNeedLayout : getNeedsSizeForLayout()) { + LayoutDependency layoutDependency = getDependency( + mightNeedLayout, direction); + layoutDependency.removeLayoutBlocker(connector); + } + } + + private void propagatePotentialLayout() { + for (ComponentConnector sizeMightChange : getResizedByLayout()) { + LayoutDependency layoutDependency = getDependency( + sizeMightChange, direction); + layoutDependency.addMeasureBlocker(connector); + } + } + + private Collection<ComponentConnector> getResizedByLayout() { + // Components that might get resized by a layout of this component + + // Parent never resized + // Connector itself resized if undefined + // Children resized if relative + + ArrayList<ComponentConnector> resized = new ArrayList<ComponentConnector>(); + if (isUndefinedInDirection(connector, direction)) { + resized.add(connector); + } + + if (connector instanceof ComponentContainerConnector) { + ComponentContainerConnector container = (ComponentContainerConnector) connector; + for (ComponentConnector child : container.getChildren()) { + if (isRelativeInDirection(child, direction)) { + resized.add(child); + } + } + } + + return resized; + } + + private void propagateNoUpcomingLayout() { + for (ComponentConnector sizeMightChange : getResizedByLayout()) { + LayoutDependency layoutDependency = getDependency( + sizeMightChange, direction); + layoutDependency.removeMeasureBlocker(connector); + } + } + + public void markSizeAsChanged() { + // When the size has changed, all that use that size should be + // layouted + for (ComponentConnector connector : getNeedsSizeForLayout()) { + LayoutDependency layoutDependency = getDependency(connector, + direction); + if (connector instanceof ManagedLayout) { + layoutDependency.setNeedsLayout(true); + } else { + // Should simulate setNeedsLayout(true) + markAsLayouted -> + // propagate needs measure + layoutDependency.propagatePostLayoutMeasure(); + } + } + + // Should also go through the hierarchy to discover appeared or + // disappeared scrollbars + ComponentConnector scrollingBoundary = getScrollingBoundary(connector); + if (scrollingBoundary != null) { + getDependency(scrollingBoundary, getOppositeDirection()) + .setNeedsMeasure(true); + } + + } + + /** + * Go up the hierarchy to find a component whose size might have changed + * in the other direction because changes to this component causes + * scrollbars to appear or disappear. + * + * @return + */ + private LayoutDependency findPotentiallyChangedScrollbar() { + ComponentConnector currentConnector = connector; + while (true) { + ComponentContainerConnector parent = currentConnector + .getParent(); + if (parent == null) { + return null; + } + if (parent instanceof MayScrollChildren) { + return getDependency(currentConnector, + getOppositeDirection()); + } + currentConnector = parent; + } + } + + private int getOppositeDirection() { + return direction == HORIZONTAL ? VERTICAL : HORIZONTAL; + } + + public void markAsLayouted() { + if (!layoutBlockers.isEmpty()) { + // Don't do anything if there are layout blockers (SimpleLayout + // gets layouted in both directions even if one direction is + // blocked) + return; + } + setNeedsLayout(false); + propagatePostLayoutMeasure(); + } + + private void propagatePostLayoutMeasure() { + for (ComponentConnector resized : getResizedByLayout()) { + LayoutDependency layoutDependency = getDependency(resized, + direction); + layoutDependency.setNeedsMeasure(true); + } + + // Special case for e.g. wrapping texts + if (direction == HORIZONTAL && !connector.isUndefinedWidth() + && connector.isUndefinedHeight()) { + LayoutDependency dependency = getDependency(connector, VERTICAL); + dependency.setNeedsMeasure(true); + } + } + + @Override + public String toString() { + String s = getCompactConnectorString(connector) + "\n"; + if (direction == VERTICAL) { + s += "Vertical"; + } else { + s += "Horizontal"; + } + ComponentState state = connector.getState(); + s += " sizing: " + + getSizeDefinition(direction == VERTICAL ? state + .getHeight() : state.getWidth()) + "\n"; + + if (needsLayout) { + s += "Needs layout\n"; + } + if (getLayoutQueue(direction).contains(connector)) { + s += "In layout queue\n"; + } + s += "Layout blockers: " + blockersToString(layoutBlockers) + "\n"; + + if (needsMeasure) { + s += "Needs measure\n"; + } + if (getMeasureQueue(direction).contains(connector)) { + s += "In measure queue\n"; + } + s += "Measure blockers: " + blockersToString(measureBlockers); + + return s; + } + + public boolean noMoreChangesExpected() { + return !needsLayout && !needsMeasure && layoutBlockers.isEmpty() + && measureBlockers.isEmpty(); + } + + } + + private static final int HORIZONTAL = 0; + private static final int VERTICAL = 1; + + private final Map<?, ?>[] dependenciesInDirection = new Map<?, ?>[] { + new HashMap<ComponentConnector, LayoutDependency>(), + new HashMap<ComponentConnector, LayoutDependency>() }; + + private final Collection<?>[] measureQueueInDirection = new HashSet<?>[] { + new HashSet<ComponentConnector>(), + new HashSet<ComponentConnector>() }; + + private final Collection<?>[] layoutQueueInDirection = new HashSet<?>[] { + new HashSet<ComponentConnector>(), + new HashSet<ComponentConnector>() }; + + public void setNeedsMeasure(ComponentConnector connector, + boolean needsMeasure) { + setNeedsHorizontalMeasure(connector, needsMeasure); + setNeedsVerticalMeasure(connector, needsMeasure); + } + + public void setNeedsHorizontalMeasure(ComponentConnector connector, + boolean needsMeasure) { + LayoutDependency dependency = getDependency(connector, HORIZONTAL); + dependency.setNeedsMeasure(needsMeasure); + } + + public void setNeedsVerticalMeasure(ComponentConnector connector, + boolean needsMeasure) { + LayoutDependency dependency = getDependency(connector, VERTICAL); + dependency.setNeedsMeasure(needsMeasure); + } + + private LayoutDependency getDependency(ComponentConnector connector, + int direction) { + @SuppressWarnings("unchecked") + Map<ComponentConnector, LayoutDependency> dependencies = (Map<ComponentConnector, LayoutDependency>) dependenciesInDirection[direction]; + LayoutDependency dependency = dependencies.get(connector); + if (dependency == null) { + dependency = new LayoutDependency(connector, direction); + dependencies.put(connector, dependency); + } + return dependency; + } + + @SuppressWarnings("unchecked") + private Collection<ManagedLayout> getLayoutQueue(int direction) { + return (Collection<ManagedLayout>) layoutQueueInDirection[direction]; + } + + @SuppressWarnings("unchecked") + private Collection<ComponentConnector> getMeasureQueue(int direction) { + return (Collection<ComponentConnector>) measureQueueInDirection[direction]; + } + + public void setNeedsHorizontalLayout(ManagedLayout layout, + boolean needsLayout) { + LayoutDependency dependency = getDependency(layout, HORIZONTAL); + dependency.setNeedsLayout(needsLayout); + } + + public void setNeedsVerticalLayout(ManagedLayout layout, boolean needsLayout) { + LayoutDependency dependency = getDependency(layout, VERTICAL); + dependency.setNeedsLayout(needsLayout); + } + + public void markAsHorizontallyLayouted(ManagedLayout layout) { + LayoutDependency dependency = getDependency(layout, HORIZONTAL); + dependency.markAsLayouted(); + } + + public void markAsVerticallyLayouted(ManagedLayout layout) { + LayoutDependency dependency = getDependency(layout, VERTICAL); + dependency.markAsLayouted(); + } + + public void markHeightAsChanged(ComponentConnector connector) { + LayoutDependency dependency = getDependency(connector, VERTICAL); + dependency.markSizeAsChanged(); + } + + public void markWidthAsChanged(ComponentConnector connector) { + LayoutDependency dependency = getDependency(connector, HORIZONTAL); + dependency.markSizeAsChanged(); + } + + private static boolean isRelativeInDirection(ComponentConnector connector, + int direction) { + if (direction == HORIZONTAL) { + return connector.isRelativeWidth(); + } else { + return connector.isRelativeHeight(); + } + } + + private static boolean isUndefinedInDirection(ComponentConnector connector, + int direction) { + if (direction == VERTICAL) { + return connector.isUndefinedHeight(); + } else { + return connector.isUndefinedWidth(); + } + } + + private static String getCompactConnectorString(ComponentConnector connector) { + return Util.getSimpleName(connector) + " (" + + connector.getConnectorId() + ")"; + } + + private static String getSizeDefinition(String size) { + if (size == null || size.length() == 0) { + return "undefined"; + } else if (size.endsWith("%")) { + return "relative"; + } else { + return "fixed"; + } + } + + private static String blockersToString( + Collection<ComponentConnector> blockers) { + StringBuilder b = new StringBuilder("["); + for (ComponentConnector blocker : blockers) { + if (b.length() != 1) { + b.append(", "); + } + b.append(getCompactConnectorString(blocker)); + } + b.append(']'); + return b.toString(); + } + + public boolean hasConnectorsToMeasure() { + return !measureQueueInDirection[HORIZONTAL].isEmpty() + || !measureQueueInDirection[VERTICAL].isEmpty(); + } + + public boolean hasHorizontalConnectorToLayout() { + return !getLayoutQueue(HORIZONTAL).isEmpty(); + } + + public boolean hasVerticaConnectorToLayout() { + return !getLayoutQueue(VERTICAL).isEmpty(); + } + + public ManagedLayout[] getHorizontalLayoutTargets() { + Collection<ManagedLayout> queue = getLayoutQueue(HORIZONTAL); + return queue.toArray(new ManagedLayout[queue.size()]); + } + + public ManagedLayout[] getVerticalLayoutTargets() { + Collection<ManagedLayout> queue = getLayoutQueue(VERTICAL); + return queue.toArray(new ManagedLayout[queue.size()]); + } + + public Collection<ComponentConnector> getMeasureTargets() { + Collection<ComponentConnector> measureTargets = new HashSet<ComponentConnector>( + getMeasureQueue(HORIZONTAL)); + measureTargets.addAll(getMeasureQueue(VERTICAL)); + return measureTargets; + } + + public void logDependencyStatus(ComponentConnector connector) { + VConsole.log("===="); + VConsole.log(getDependency(connector, HORIZONTAL).toString()); + VConsole.log(getDependency(connector, VERTICAL).toString()); + } + + public boolean noMoreChangesExpected(ComponentConnector connector) { + return getDependency(connector, HORIZONTAL).noMoreChangesExpected() + && getDependency(connector, VERTICAL).noMoreChangesExpected(); + } + + public ComponentConnector getScrollingBoundary(ComponentConnector connector) { + LayoutDependency dependency = getDependency(connector, HORIZONTAL); + if (!dependency.scrollingParentCached) { + ComponentContainerConnector parent = dependency.connector + .getParent(); + if (parent instanceof MayScrollChildren) { + dependency.scrollingBoundary = connector; + } else if (parent != null) { + dependency.scrollingBoundary = getScrollingBoundary(parent); + } else { + // No scrolling parent + } + + dependency.scrollingParentCached = true; + } + return dependency.scrollingBoundary; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/MayScrollChildren.java b/src/com/vaadin/terminal/gwt/client/ui/layout/MayScrollChildren.java new file mode 100644 index 0000000000..62c9937c4c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/MayScrollChildren.java @@ -0,0 +1,10 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.layout; + +import com.vaadin.terminal.gwt.client.ComponentContainerConnector; + +public interface MayScrollChildren extends ComponentContainerConnector { + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/VLayoutSlot.java b/src/com/vaadin/terminal/gwt/client/ui/layout/VLayoutSlot.java new file mode 100644 index 0000000000..034fe35649 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/VLayoutSlot.java @@ -0,0 +1,287 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.layout; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; + +public abstract class VLayoutSlot { + + private final Element wrapper = Document.get().createDivElement().cast(); + + private AlignmentInfo alignment; + private VCaption caption; + private final Widget widget; + + private double expandRatio; + + public VLayoutSlot(String baseClassName, Widget widget) { + this.widget = widget; + + wrapper.setClassName(baseClassName + "-slot"); + } + + public VCaption getCaption() { + return caption; + } + + public void setCaption(VCaption caption) { + if (this.caption != null) { + this.caption.removeFromParent(); + } + this.caption = caption; + if (caption != null) { + // Physical attach. + DOM.insertBefore(wrapper, caption.getElement(), widget.getElement()); + Style style = caption.getElement().getStyle(); + style.setPosition(Position.ABSOLUTE); + style.setTop(0, Unit.PX); + } + } + + public AlignmentInfo getAlignment() { + return alignment; + } + + public Widget getWidget() { + return widget; + } + + public void setAlignment(AlignmentInfo alignment) { + this.alignment = alignment; + } + + public void positionHorizontally(double currentLocation, + double allocatedSpace, double marginRight) { + Style style = wrapper.getStyle(); + + double availableWidth = allocatedSpace; + + VCaption caption = getCaption(); + Style captionStyle = caption != null ? caption.getElement().getStyle() + : null; + int captionWidth = getCaptionWidth(); + + boolean captionAboveCompnent; + if (caption == null) { + captionAboveCompnent = false; + style.clearPaddingRight(); + } else { + captionAboveCompnent = !caption.shouldBePlacedAfterComponent(); + if (!captionAboveCompnent) { + availableWidth -= captionWidth; + captionStyle.clearLeft(); + captionStyle.setRight(0, Unit.PX); + style.setPaddingRight(captionWidth, Unit.PX); + } else { + captionStyle.setLeft(0, Unit.PX); + captionStyle.clearRight(); + style.clearPaddingRight(); + } + } + + if (marginRight > 0) { + style.setMarginRight(marginRight, Unit.PX); + } else { + style.clearMarginRight(); + } + + if (isRelativeWidth()) { + style.setPropertyPx("width", (int) availableWidth); + } else { + style.clearProperty("width"); + } + + double allocatedContentWidth = 0; + if (isRelativeWidth()) { + String percentWidth = getWidget().getElement().getStyle() + .getWidth(); + double percentage = parsePercent(percentWidth); + allocatedContentWidth = availableWidth * (percentage / 100); + reportActualRelativeWidth(Math.round((float) allocatedContentWidth)); + } + + AlignmentInfo alignment = getAlignment(); + if (!alignment.isLeft()) { + double usedWidth; + if (isRelativeWidth()) { + usedWidth = allocatedContentWidth; + } else { + usedWidth = getWidgetWidth(); + } + if (alignment.isHorizontalCenter()) { + currentLocation += (allocatedSpace - usedWidth) / 2d; + if (captionAboveCompnent) { + captionStyle.setLeft( + Math.round(usedWidth - captionWidth) / 2, Unit.PX); + } + } else { + currentLocation += (allocatedSpace - usedWidth); + if (captionAboveCompnent) { + captionStyle.setLeft(Math.round(usedWidth - captionWidth), + Unit.PX); + } + } + } else { + if (captionAboveCompnent) { + captionStyle.setLeft(0, Unit.PX); + } + } + + style.setLeft(Math.round(currentLocation), Unit.PX); + } + + private double parsePercent(String size) { + return Double.parseDouble(size.replaceAll("%", "")); + } + + public void positionVertically(double currentLocation, + double allocatedSpace, double marginBottom) { + Style style = wrapper.getStyle(); + + double contentHeight = allocatedSpace; + + int captionHeight; + VCaption caption = getCaption(); + if (caption == null || caption.shouldBePlacedAfterComponent()) { + style.clearPaddingTop(); + captionHeight = 0; + } else { + captionHeight = getCaptionHeight(); + contentHeight -= captionHeight; + if (contentHeight < 0) { + contentHeight = 0; + } + style.setPaddingTop(captionHeight, Unit.PX); + } + + if (marginBottom > 0) { + style.setMarginBottom(marginBottom, Unit.PX); + } else { + style.clearMarginBottom(); + } + + if (isRelativeHeight()) { + style.setHeight(contentHeight, Unit.PX); + } else { + style.clearHeight(); + } + + double allocatedContentHeight = 0; + if (isRelativeHeight()) { + String height = getWidget().getElement().getStyle().getHeight(); + double percentage = parsePercent(height); + allocatedContentHeight = contentHeight * (percentage / 100); + reportActualRelativeHeight(Math + .round((float) allocatedContentHeight)); + } + + AlignmentInfo alignment = getAlignment(); + if (!alignment.isTop()) { + double usedHeight; + if (isRelativeHeight()) { + usedHeight = captionHeight + allocatedContentHeight; + } else { + usedHeight = getUsedHeight(); + } + if (alignment.isVerticalCenter()) { + currentLocation += (allocatedSpace - usedHeight) / 2d; + } else { + currentLocation += (allocatedSpace - usedHeight); + } + } + + style.setTop(currentLocation, Unit.PX); + } + + protected void reportActualRelativeHeight(int allocatedHeight) { + // Default implementation does nothing + } + + protected void reportActualRelativeWidth(int allocatedWidth) { + // Default implementation does nothing + } + + public void positionInDirection(double currentLocation, + double allocatedSpace, double endingMargin, boolean isVertical) { + if (isVertical) { + positionVertically(currentLocation, allocatedSpace, endingMargin); + } else { + positionHorizontally(currentLocation, allocatedSpace, endingMargin); + } + } + + public int getWidgetSizeInDirection(boolean isVertical) { + return isVertical ? getWidgetHeight() : getWidgetWidth(); + } + + public int getUsedWidth() { + int widgetWidth = getWidgetWidth(); + if (caption == null) { + return widgetWidth; + } else if (caption.shouldBePlacedAfterComponent()) { + return widgetWidth + getCaptionWidth(); + } else { + return Math.max(widgetWidth, getCaptionWidth()); + } + } + + public int getUsedHeight() { + int widgetHeight = getWidgetHeight(); + if (caption == null) { + return widgetHeight; + } else if (caption.shouldBePlacedAfterComponent()) { + return Math.max(widgetHeight, getCaptionHeight()); + } else { + return widgetHeight + getCaptionHeight(); + } + } + + public int getUsedSizeInDirection(boolean isVertical) { + return isVertical ? getUsedHeight() : getUsedWidth(); + } + + protected abstract int getCaptionHeight(); + + protected abstract int getCaptionWidth(); + + public abstract int getWidgetHeight(); + + public abstract int getWidgetWidth(); + + public abstract boolean isUndefinedHeight(); + + public abstract boolean isUndefinedWidth(); + + public boolean isUndefinedInDirection(boolean isVertical) { + return isVertical ? isUndefinedHeight() : isUndefinedWidth(); + } + + public abstract boolean isRelativeHeight(); + + public abstract boolean isRelativeWidth(); + + public boolean isRelativeInDirection(boolean isVertical) { + return isVertical ? isRelativeHeight() : isRelativeWidth(); + } + + public Element getWrapperElement() { + return wrapper; + } + + public void setExpandRatio(double expandRatio) { + this.expandRatio = expandRatio; + } + + public double getExpandRatio() { + return expandRatio; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/link/LinkConnector.java b/src/com/vaadin/terminal/gwt/client/ui/link/LinkConnector.java new file mode 100644 index 0000000000..f74a851d03 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/link/LinkConnector.java @@ -0,0 +1,99 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.link; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.ui.Link; + +@Connect(Link.class) +public class LinkConnector extends AbstractComponentConnector implements + Paintable { + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().client = client; + + getWidget().enabled = isEnabled(); + + if (uidl.hasAttribute("name")) { + getWidget().target = uidl.getStringAttribute("name"); + getWidget().anchor.setAttribute("target", getWidget().target); + } + if (uidl.hasAttribute("src")) { + getWidget().src = client.translateVaadinUri(uidl + .getStringAttribute("src")); + getWidget().anchor.setAttribute("href", getWidget().src); + } + + if (uidl.hasAttribute("border")) { + if ("none".equals(uidl.getStringAttribute("border"))) { + getWidget().borderStyle = VLink.BORDER_STYLE_NONE; + } else { + getWidget().borderStyle = VLink.BORDER_STYLE_MINIMAL; + } + } else { + getWidget().borderStyle = VLink.BORDER_STYLE_DEFAULT; + } + + getWidget().targetHeight = uidl.hasAttribute("targetHeight") ? uidl + .getIntAttribute("targetHeight") : -1; + getWidget().targetWidth = uidl.hasAttribute("targetWidth") ? uidl + .getIntAttribute("targetWidth") : -1; + + // Set link caption + getWidget().captionElement.setInnerText(getState().getCaption()); + + // handle error + if (null != getState().getErrorMessage()) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty(getWidget().errorIndicatorElement, + "className", "v-errorindicator"); + } + DOM.insertChild(getWidget().getElement(), + getWidget().errorIndicatorElement, 0); + } else if (getWidget().errorIndicatorElement != null) { + DOM.setStyleAttribute(getWidget().errorIndicatorElement, "display", + "none"); + } + + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(client); + getWidget().anchor.insertBefore(getWidget().icon.getElement(), + getWidget().captionElement); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VLink.class); + } + + @Override + public VLink getWidget() { + return (VLink) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/link/VLink.java b/src/com/vaadin/terminal/gwt/client/ui/link/VLink.java new file mode 100644 index 0000000000..68fe5d9292 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/link/VLink.java @@ -0,0 +1,117 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.link; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Icon; + +public class VLink extends HTML implements ClickHandler { + + public static final String CLASSNAME = "v-link"; + + protected static final int BORDER_STYLE_DEFAULT = 0; + protected static final int BORDER_STYLE_MINIMAL = 1; + protected static final int BORDER_STYLE_NONE = 2; + + protected String src; + + protected String target; + + protected int borderStyle = BORDER_STYLE_DEFAULT; + + protected boolean enabled; + + protected int targetWidth; + + protected int targetHeight; + + protected Element errorIndicatorElement; + + protected final Element anchor = DOM.createAnchor(); + + protected final Element captionElement = DOM.createSpan(); + + protected Icon icon; + + protected ApplicationConnection client; + + public VLink() { + super(); + getElement().appendChild(anchor); + anchor.appendChild(captionElement); + addClickHandler(this); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + setStyleName(CLASSNAME); + } + + public void onClick(ClickEvent event) { + if (enabled) { + if (target == null) { + target = "_self"; + } + String features; + switch (borderStyle) { + case BORDER_STYLE_NONE: + features = "menubar=no,location=no,status=no"; + break; + case BORDER_STYLE_MINIMAL: + features = "menubar=yes,location=no,status=no"; + break; + default: + features = ""; + break; + } + + if (targetWidth > 0) { + features += (features.length() > 0 ? "," : "") + "width=" + + targetWidth; + } + if (targetHeight > 0) { + features += (features.length() > 0 ? "," : "") + "height=" + + targetHeight; + } + + if (features.length() > 0) { + // if 'special features' are set, use window.open(), unless + // a modifier key is held (ctrl to open in new tab etc) + Event e = DOM.eventGetCurrentEvent(); + if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey() + && !e.getMetaKey()) { + Window.open(src, target, features); + e.preventDefault(); + } + } + } + } + + @Override + public void onBrowserEvent(Event event) { + final Element target = DOM.eventGetTarget(event); + if (event.getTypeInt() == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + } + if (client != null) { + client.handleTooltipEvent(event, this); + } + if (target == captionElement || target == anchor + || (icon != null && target == icon.getElement())) { + super.onBrowserEvent(event); + } + if (!enabled) { + event.preventDefault(); + } + + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/listselect/ListSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/listselect/ListSelectConnector.java new file mode 100644 index 0000000000..ddaa959560 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/listselect/ListSelectConnector.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.listselect; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.ui.ListSelect; + +@Connect(ListSelect.class) +public class ListSelectConnector extends OptionGroupBaseConnector { + + @Override + protected Widget createWidget() { + return GWT.create(VListSelect.class); + } + + @Override + public VListSelect getWidget() { + return (VListSelect) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/listselect/TooltipListBox.java b/src/com/vaadin/terminal/gwt/client/ui/listselect/TooltipListBox.java new file mode 100644 index 0000000000..abecd844da --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/listselect/TooltipListBox.java @@ -0,0 +1,41 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.listselect; + +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.VTooltip; + +/** + * Extended ListBox to listen tooltip events and forward them to generic + * handler. + */ +public class TooltipListBox extends ListBox { + private ApplicationConnection client; + private Widget widget; + + public TooltipListBox(boolean isMultiselect) { + super(isMultiselect); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + public void setClient(ApplicationConnection client) { + this.client = client; + } + + public void setSelect(Widget widget) { + this.widget = widget; + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, widget); + } + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java b/src/com/vaadin/terminal/gwt/client/ui/listselect/VListSelect.java index cebc4600a2..e338897841 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/listselect/VListSelect.java @@ -2,18 +2,14 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.listselect; import java.util.ArrayList; import java.util.Iterator; import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.ListBox; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroupBase; public class VListSelect extends VOptionGroupBase { @@ -80,11 +76,11 @@ public class VListSelect extends VOptionGroupBase { } else { lastSelectedIndex = si; if (isMultiselect()) { - client.updateVariable(id, "selected", getSelectedItems(), - isImmediate()); + client.updateVariable(paintableId, "selected", + getSelectedItems(), isImmediate()); } else { - client.updateVariable(id, "selected", new String[] { "" - + getSelectedItem() }, isImmediate()); + client.updateVariable(paintableId, "selected", + new String[] { "" + getSelectedItem() }, isImmediate()); } } } @@ -109,35 +105,4 @@ public class VListSelect extends VOptionGroupBase { public void focus() { select.setFocus(true); } - -} - -/** - * Extended ListBox to listen tooltip events and forward them to generic - * handler. - */ -class TooltipListBox extends ListBox { - private ApplicationConnection client; - private Paintable pntbl; - - TooltipListBox(boolean isMultiselect) { - super(isMultiselect); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - public void setClient(ApplicationConnection client) { - this.client = client; - } - - public void setSelect(Paintable s) { - pntbl = s; - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, pntbl); - } - } }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/MenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBar.java index f0857f48c1..7bee870387 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/MenuBar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBar.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.menubar; /* * Copyright 2007 Google Inc. @@ -32,6 +32,7 @@ import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.PopupListener; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ui.VOverlay; /** * A standard menu bar widget. A menu bar can contain any number of menu items, @@ -294,7 +295,7 @@ public class MenuBar extends Widget implements PopupListener { * @return a list containing the <code>MenuItem</code> objects in the menu * bar */ - protected List<MenuItem> getItems() { + public List<MenuItem> getItems() { return items; } @@ -306,7 +307,7 @@ public class MenuBar extends Widget implements PopupListener { * @return the <code>MenuItem</code> that is currently selected, or * <code>null</code> if no items are currently selected */ - protected MenuItem getSelectedItem() { + public MenuItem getSelectedItem() { return selectedItem; } @@ -347,7 +348,7 @@ public class MenuBar extends Widget implements PopupListener { * <code>true</code> if the item's command should be fired, * <code>false</code> otherwise. */ - void doItemAction(final MenuItem item, boolean fireCommand) { + protected void doItemAction(final MenuItem item, boolean fireCommand) { // If the given item is already showing its menu, we're done. if ((shownChildMenu != null) && (item.getSubMenu() == shownChildMenu)) { return; @@ -451,7 +452,7 @@ public class MenuBar extends Widget implements PopupListener { } } - void selectItem(MenuItem item) { + public void selectItem(MenuItem item) { if (item == selectedItem) { return; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBarConnector.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBarConnector.java new file mode 100644 index 0000000000..d063c89ca9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBarConnector.java @@ -0,0 +1,169 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.menubar; + +import java.util.Iterator; +import java.util.Stack; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.menubar.VMenuBar.CustomMenuItem; + +@Connect(value = com.vaadin.ui.MenuBar.class, loadStyle = LoadStyle.LAZY) +public class MenuBarConnector extends AbstractComponentConnector implements + Paintable, SimpleManagedLayout { + /** + * This method must be implemented to update the client-side component from + * UIDL data received from server. + * + * This method is called when the page is loaded for the first time, and + * every time UI changes in the component are received from the server. + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().htmlContentAllowed = uidl + .hasAttribute(VMenuBar.HTML_CONTENT_ALLOWED); + + getWidget().openRootOnHover = uidl + .getBooleanAttribute(VMenuBar.OPEN_ROOT_MENU_ON_HOWER); + + getWidget().enabled = isEnabled(); + + // For future connections + getWidget().client = client; + getWidget().uidlId = uidl.getId(); + + // Empty the menu every time it receives new information + if (!getWidget().getItems().isEmpty()) { + getWidget().clearItems(); + } + + UIDL options = uidl.getChildUIDL(0); + + if (null != getState() && !getState().isUndefinedWidth()) { + UIDL moreItemUIDL = options.getChildUIDL(0); + StringBuffer itemHTML = new StringBuffer(); + + if (moreItemUIDL.hasAttribute("icon")) { + itemHTML.append("<img src=\"" + + Util.escapeAttribute(client + .translateVaadinUri(moreItemUIDL + .getStringAttribute("icon"))) + + "\" class=\"" + Icon.CLASSNAME + "\" alt=\"\" />"); + } + + String moreItemText = moreItemUIDL.getStringAttribute("text"); + if ("".equals(moreItemText)) { + moreItemText = "►"; + } + itemHTML.append(moreItemText); + + getWidget().moreItem = GWT.create(CustomMenuItem.class); + getWidget().moreItem.setHTML(itemHTML.toString()); + getWidget().moreItem.setCommand(VMenuBar.emptyCommand); + + getWidget().collapsedRootItems = new VMenuBar(true, getWidget()); + getWidget().moreItem.setSubMenu(getWidget().collapsedRootItems); + getWidget().moreItem.addStyleName(VMenuBar.CLASSNAME + + "-more-menuitem"); + } + + UIDL uidlItems = uidl.getChildUIDL(1); + Iterator<Object> itr = uidlItems.getChildIterator(); + Stack<Iterator<Object>> iteratorStack = new Stack<Iterator<Object>>(); + Stack<VMenuBar> menuStack = new Stack<VMenuBar>(); + VMenuBar currentMenu = getWidget(); + + while (itr.hasNext()) { + UIDL item = (UIDL) itr.next(); + CustomMenuItem currentItem = null; + + final int itemId = item.getIntAttribute("id"); + + boolean itemHasCommand = item.hasAttribute("command"); + boolean itemIsCheckable = item + .hasAttribute(VMenuBar.ATTRIBUTE_CHECKED); + + String itemHTML = getWidget().buildItemHTML(item); + + Command cmd = null; + if (!item.hasAttribute("separator")) { + if (itemHasCommand || itemIsCheckable) { + // Construct a command that fires onMenuClick(int) with the + // item's id-number + cmd = new Command() { + public void execute() { + getWidget().hostReference.onMenuClick(itemId); + } + }; + } + } + + currentItem = currentMenu.addItem(itemHTML.toString(), cmd); + currentItem.updateFromUIDL(item, client); + + if (item.getChildCount() > 0) { + menuStack.push(currentMenu); + iteratorStack.push(itr); + itr = item.getChildIterator(); + currentMenu = new VMenuBar(true, currentMenu); + // this is the top-level style that also propagates to items - + // any item specific styles are set above in + // currentItem.updateFromUIDL(item, client) + if (getState().hasStyles()) { + for (String style : getState().getStyles()) { + currentMenu.addStyleDependentName(style); + } + } + currentItem.setSubMenu(currentMenu); + } + + while (!itr.hasNext() && !iteratorStack.empty()) { + boolean hasCheckableItem = false; + for (CustomMenuItem menuItem : currentMenu.getItems()) { + hasCheckableItem = hasCheckableItem + || menuItem.isCheckable(); + } + if (hasCheckableItem) { + currentMenu.addStyleDependentName("check-column"); + } else { + currentMenu.removeStyleDependentName("check-column"); + } + + itr = iteratorStack.pop(); + currentMenu = menuStack.pop(); + } + }// while + + getLayoutManager().setNeedsHorizontalLayout(this); + + }// updateFromUIDL + + @Override + protected Widget createWidget() { + return GWT.create(VMenuBar.class); + } + + @Override + public VMenuBar getWidget() { + return (VMenuBar) super.getWidget(); + } + + public void layout() { + getWidget().iLayout(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/MenuItem.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuItem.java index ec02db1c70..af79ba7c5e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/MenuItem.java +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuItem.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.menubar; /* * Copyright 2007 Google Inc. diff --git a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/VMenuBar.java index 12678c9515..e48483cb02 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/VMenuBar.java @@ -1,17 +1,14 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.menubar; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.Stack; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; @@ -35,16 +32,20 @@ import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ContainerResizedListener; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.LayoutManager; import com.vaadin.terminal.gwt.client.TooltipInfo; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.SimpleFocusablePanel; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.VOverlay; -public class VMenuBar extends SimpleFocusablePanel implements Paintable, - CloseHandler<PopupPanel>, ContainerResizedListener, KeyPressHandler, - KeyDownHandler, FocusHandler, SubPartAware { +public class VMenuBar extends SimpleFocusablePanel implements + CloseHandler<PopupPanel>, KeyPressHandler, KeyDownHandler, + FocusHandler, SubPartAware { // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable, // used for the root menu but also used for the sub menus. @@ -57,7 +58,6 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, protected ApplicationConnection client; protected final VMenuBar hostReference = this; - protected String submenuIcon = null; protected CustomMenuItem moreItem = null; // Only used by the root menu bar @@ -70,6 +70,10 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, public static final String OPEN_ROOT_MENU_ON_HOWER = "ormoh"; public static final String ATTRIBUTE_CHECKED = "checked"; + public static final String ATTRIBUTE_ITEM_DESCRIPTION = "description"; + public static final String ATTRIBUTE_ITEM_ICON = "icon"; + public static final String ATTRIBUTE_ITEM_DISABLED = "disabled"; + public static final String ATTRIBUTE_ITEM_STYLE = "style"; public static final String HTML_CONTENT_ALLOWED = "usehtml"; @@ -83,7 +87,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, protected VMenuBar parentMenu; protected CustomMenuItem selected; - private boolean enabled = true; + boolean enabled = true; private String width = "notinited"; @@ -95,9 +99,9 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, } }); - private boolean openRootOnHover; + boolean openRootOnHover; - private boolean htmlContentAllowed; + boolean htmlContentAllowed; public VMenuBar() { // Create an empty horizontal menubar @@ -150,31 +154,8 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, } } - @Override - public void setWidth(String width) { - if (Util.equals(this.width, width)) { - return; - } - - this.width = width; - if (BrowserInfo.get().isIE6() && width.endsWith("px")) { - // IE6 sometimes measures wrong using - // Util.setWidthExcludingPaddingAndBorder so this is extracted to a - // special case that uses another method. Really should fix the - // Util.setWidthExcludingPaddingAndBorder method but that will - // probably break additional cases - int requestedPixelWidth = Integer.parseInt(width.substring(0, - width.length() - 2)); - int paddingBorder = Util.measureHorizontalPaddingAndBorder( - getElement(), 0); - int w = requestedPixelWidth - paddingBorder; - if (w < 0) { - w = 0; - } - getElement().getStyle().setWidth(w, Unit.PX); - } else { - Util.setWidthExcludingPaddingAndBorder(this, width, 0); - } + void updateSize() { + // Take from setWidth if (!subMenu) { // Only needed for root level menu hideChildren(); @@ -184,141 +165,6 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, } /** - * This method must be implemented to update the client-side component from - * UIDL data received from server. - * - * This method is called when the page is loaded for the first time, and - * every time UI changes in the component are received from the server. - */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // This call should be made first. Ensure correct implementation, - // and let the containing layout manage caption, etc. - if (client.updateComponent(this, uidl, true)) { - return; - } - - htmlContentAllowed = uidl.hasAttribute(HTML_CONTENT_ALLOWED); - - openRootOnHover = uidl.getBooleanAttribute(OPEN_ROOT_MENU_ON_HOWER); - - enabled = !uidl.getBooleanAttribute("disabled"); - - // For future connections - this.client = client; - uidlId = uidl.getId(); - - // Empty the menu every time it receives new information - if (!getItems().isEmpty()) { - clearItems(); - } - - UIDL options = uidl.getChildUIDL(0); - - // FIXME remove in version 7 - if (options.hasAttribute("submenuIcon")) { - submenuIcon = client.translateVaadinUri(uidl.getChildUIDL(0) - .getStringAttribute("submenuIcon")); - } else { - submenuIcon = null; - } - - if (uidl.hasAttribute("width")) { - UIDL moreItemUIDL = options.getChildUIDL(0); - StringBuffer itemHTML = new StringBuffer(); - - if (moreItemUIDL.hasAttribute("icon")) { - itemHTML.append("<img src=\"" - + Util.escapeAttribute(client - .translateVaadinUri(moreItemUIDL - .getStringAttribute("icon"))) - + "\" class=\"" + Icon.CLASSNAME + "\" alt=\"\" />"); - } - - String moreItemText = moreItemUIDL.getStringAttribute("text"); - if ("".equals(moreItemText)) { - moreItemText = "►"; - } - itemHTML.append(moreItemText); - - moreItem = GWT.create(CustomMenuItem.class); - moreItem.setHTML(itemHTML.toString()); - moreItem.setCommand(emptyCommand); - - collapsedRootItems = new VMenuBar(true, - (VMenuBar) client.getPaintable(uidlId)); - moreItem.setSubMenu(collapsedRootItems); - moreItem.addStyleName(CLASSNAME + "-more-menuitem"); - } - - UIDL uidlItems = uidl.getChildUIDL(1); - Iterator<Object> itr = uidlItems.getChildIterator(); - Stack<Iterator<Object>> iteratorStack = new Stack<Iterator<Object>>(); - Stack<VMenuBar> menuStack = new Stack<VMenuBar>(); - VMenuBar currentMenu = this; - - while (itr.hasNext()) { - UIDL item = (UIDL) itr.next(); - CustomMenuItem currentItem = null; - - final int itemId = item.getIntAttribute("id"); - - boolean itemHasCommand = item.hasAttribute("command"); - boolean itemIsCheckable = item.hasAttribute(ATTRIBUTE_CHECKED); - - String itemHTML = buildItemHTML(item); - - Command cmd = null; - if (!item.hasAttribute("separator")) { - if (itemHasCommand || itemIsCheckable) { - // Construct a command that fires onMenuClick(int) with the - // item's id-number - cmd = new Command() { - public void execute() { - hostReference.onMenuClick(itemId); - } - }; - } - } - - currentItem = currentMenu.addItem(itemHTML.toString(), cmd); - currentItem.updateFromUIDL(item, client); - - if (item.getChildCount() > 0) { - menuStack.push(currentMenu); - iteratorStack.push(itr); - itr = item.getChildIterator(); - currentMenu = new VMenuBar(true, currentMenu); - if (uidl.hasAttribute("style")) { - for (String style : uidl.getStringAttribute("style").split( - " ")) { - currentMenu.addStyleDependentName(style); - } - } - currentItem.setSubMenu(currentMenu); - } - - while (!itr.hasNext() && !iteratorStack.empty()) { - boolean hasCheckableItem = false; - for (CustomMenuItem menuItem : currentMenu.getItems()) { - hasCheckableItem = hasCheckableItem - || menuItem.isCheckable(); - } - if (hasCheckableItem) { - currentMenu.addStyleDependentName("check-column"); - } else { - currentMenu.removeStyleDependentName("check-column"); - } - - itr = iteratorStack.pop(); - currentMenu = menuStack.pop(); - } - }// while - - iLayout(false); - - }// updateFromUIDL - - /** * Build the HTML content for a menu item. * * @param item @@ -332,13 +178,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, } else { // Add submenu indicator if (item.getChildCount() > 0) { - // FIXME For compatibility reasons: remove in version 7 String bgStyle = ""; - if (submenuIcon != null) { - bgStyle = " style=\"background-image: url(" - + Util.escapeAttribute(submenuIcon) - + "); text-indent: -999px; width: 1em;\""; - } itemHTML.append("<span class=\"" + CLASSNAME + "-submenu-indicator\"" + bgStyle + ">►</span>"); } @@ -478,9 +318,6 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, // Handle onload events (icon loaded, size changes) if (DOM.eventGetType(e) == Event.ONLOAD) { - if (BrowserInfo.get().isIE6()) { - Util.doIE6PngFix((Element) Element.as(e.getEventTarget())); - } VMenuBar parent = getParentMenu(); if (parent != null) { // The onload event for an image in a popup should be sent to @@ -733,27 +570,6 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, popup.setPopupPosition(left, top); - // IE7 really tests one's patience sometimes - // Part of a fix to correct #3850 - if (BrowserInfo.get().isIE7()) { - popup.getElement().getStyle().setProperty("zoom", ""); - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - if (popup == null) { - // The child menu can be hidden before this command is - // run. - return; - } - - if (popup.getElement().getStyle().getProperty("width") == null - || popup.getElement().getStyle() - .getProperty("width") == "") { - popup.setWidth(popup.getOffsetWidth() + "px"); - } - popup.getElement().getStyle().setProperty("zoom", "1"); - } - }); - } } private int adjustPopupHeight(int top, final int shadowSpace) { @@ -780,19 +596,10 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, style.setHeight(availableHeight, Unit.PX); style.setOverflowY(Overflow.SCROLL); - // Make room for the scroll bar - if (BrowserInfo.get().isIE6()) { - // IE6 renders the sub menu arrow icons on the scroll bar - // unless we add some padding - style.setPaddingRight(Util.getNativeScrollbarSize(), - Unit.PX); - } else { - // For other browsers, adjusting the width of the popup is - // enough - style.setWidth( - contentWidth + Util.getNativeScrollbarSize(), - Unit.PX); - } + // Make room for the scroll bar by adjusting the width of the + // popup + style.setWidth(contentWidth + Util.getNativeScrollbarSize(), + Unit.PX); popup.updateShadowSizeAndPosition(); } } @@ -962,6 +769,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, addStyleDependentName("selected"); // needed for IE6 to have a single style name to match for an // element + // TODO Can be optimized now that IE6 is not supported any more if (checkable) { if (checked) { removeStyleDependentName("selected-unchecked"); @@ -1092,7 +900,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { this.client = client; setSeparator(uidl.hasAttribute("separator")); - setEnabled(!uidl.hasAttribute("disabled")); + setEnabled(!uidl.hasAttribute(ATTRIBUTE_ITEM_DISABLED)); if (!isSeparator() && uidl.hasAttribute(ATTRIBUTE_CHECKED)) { // if the selected attribute is present (either true or false), @@ -1103,13 +911,15 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, setCheckable(false); } - if (uidl.hasAttribute("style")) { - String itemStyle = uidl.getStringAttribute("style"); + if (uidl.hasAttribute(ATTRIBUTE_ITEM_STYLE)) { + String itemStyle = uidl + .getStringAttribute(ATTRIBUTE_ITEM_STYLE); addStyleDependentName(itemStyle); } - if (uidl.hasAttribute("description")) { - String description = uidl.getStringAttribute("description"); + if (uidl.hasAttribute(ATTRIBUTE_ITEM_DESCRIPTION)) { + String description = uidl + .getStringAttribute(ATTRIBUTE_ITEM_DESCRIPTION); TooltipInfo info = new TooltipInfo(description); VMenuBar root = findRootMenu(); @@ -1150,10 +960,9 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, /** * @author Jouni Koivuviita / Vaadin Ltd. */ - private int paddingWidth = -1; - public void iLayout() { iLayout(false); + updateSize(); } public void iLayout(boolean iconLoadEvent) { @@ -1172,28 +981,8 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, removeItem(moreItem); } - // Measure available space - if (paddingWidth == -1) { - int widthBefore = getElement().getClientWidth(); - getElement().getStyle().setProperty("padding", "0"); - paddingWidth = widthBefore - getElement().getClientWidth(); - getElement().getStyle().setProperty("padding", ""); - } - String overflow = ""; - if (BrowserInfo.get().isIE6()) { - // IE6 cannot measure available width correctly without - // overflow:hidden - overflow = getElement().getStyle().getProperty("overflow"); - getElement().getStyle().setProperty("overflow", "hidden"); - } - - int availableWidth = getElement().getClientWidth() - paddingWidth; - - if (BrowserInfo.get().isIE6()) { - // IE6 cannot measure available width correctly without - // overflow:hidden - getElement().getStyle().setProperty("overflow", overflow); - } + int availableWidth = LayoutManager.get(client).getInnerWidth( + getElement()); // Used width includes the "more" item if present int usedWidth = getConsumedWidth(); @@ -1237,17 +1026,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, removeItem(expand); collapsedRootItems.addItem(expand, 0); } else { - widthAvailable = diff; - } - - if (BrowserInfo.get().isIE6()) { - /* - * Handle transparency for IE6 here as we cannot - * implement it in CustomMenuItem.onAttach because - * onAttach is never called for CustomMenuItem due to an - * invalid component hierarchy (#6203)... - */ - reloadImages(expand.getElement()); + widthAvailable = diff + moreItemWidth; } } } @@ -1656,31 +1435,4 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, return null; } - @Override - protected void onLoad() { - super.onLoad(); - if (BrowserInfo.get().isIE6()) { - reloadImages(getElement()); - } - } - - /** - * Force a new onload event for all images. Used only for IE6 to deal with - * PNG transparency. - */ - private void reloadImages(Element root) { - - NodeList<com.google.gwt.dom.client.Element> imgElements = root - .getElementsByTagName("img"); - for (int i = 0; i < imgElements.getLength(); i++) { - Element e = (Element) imgElements.getItem(i); - - // IE6 fires onload events for the icons before the listener - // is attached (or never). Updating the src force another - // onload event - String src = e.getAttribute("src"); - e.setAttribute("src", src); - } - } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java new file mode 100644 index 0000000000..801c405826 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java @@ -0,0 +1,125 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.nativebutton; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.EventHelper; +import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.button.ButtonServerRpc; +import com.vaadin.terminal.gwt.client.ui.button.ButtonState; +import com.vaadin.ui.NativeButton; + +@Connect(NativeButton.class) +public class NativeButtonConnector extends AbstractComponentConnector implements + BlurHandler, FocusHandler { + + private HandlerRegistration focusHandlerRegistration; + private HandlerRegistration blurHandlerRegistration; + + private FocusAndBlurServerRpc focusBlurRpc = RpcProxy.create( + FocusAndBlurServerRpc.class, this); + + @Override + public void init() { + super.init(); + + getWidget().buttonRpcProxy = RpcProxy.create(ButtonServerRpc.class, + this); + getWidget().client = getConnection(); + getWidget().paintableId = getConnectorId(); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().disableOnClick = getState().isDisableOnClick(); + focusHandlerRegistration = EventHelper.updateFocusHandler(this, + focusHandlerRegistration); + blurHandlerRegistration = EventHelper.updateBlurHandler(this, + blurHandlerRegistration); + + // Set text + getWidget().setText(getState().getCaption()); + + // handle error + if (null != getState().getErrorMessage()) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createSpan(); + getWidget().errorIndicatorElement + .setClassName("v-errorindicator"); + } + getWidget().getElement().insertBefore( + getWidget().errorIndicatorElement, + getWidget().captionElement); + + } else if (getWidget().errorIndicatorElement != null) { + getWidget().getElement().removeChild( + getWidget().errorIndicatorElement); + getWidget().errorIndicatorElement = null; + } + + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(getConnection()); + getWidget().getElement().insertBefore( + getWidget().icon.getElement(), + getWidget().captionElement); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + } else { + if (getWidget().icon != null) { + getWidget().getElement().removeChild( + getWidget().icon.getElement()); + getWidget().icon = null; + } + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VNativeButton.class); + } + + @Override + public VNativeButton getWidget() { + return (VNativeButton) super.getWidget(); + } + + @Override + public ButtonState getState() { + return (ButtonState) super.getState(); + } + + public void onFocus(FocusEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurRpc.focus(); + } + + public void onBlur(BlurEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurRpc.blur(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java new file mode 100644 index 0000000000..d0b8f73eb1 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java @@ -0,0 +1,132 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.nativebutton; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Button; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.button.ButtonServerRpc; + +public class VNativeButton extends Button implements ClickHandler { + + public static final String CLASSNAME = "v-nativebutton"; + + protected String width = null; + + protected String paintableId; + + protected ApplicationConnection client; + + ButtonServerRpc buttonRpcProxy; + + protected Element errorIndicatorElement; + + protected final Element captionElement = DOM.createSpan(); + + protected Icon icon; + + /** + * Helper flag to handle special-case where the button is moved from under + * mouse while clicking it. In this case mouse leaves the button without + * moving. + */ + private boolean clickPending; + + protected boolean disableOnClick = false; + + public VNativeButton() { + setStyleName(CLASSNAME); + + getElement().appendChild(captionElement); + captionElement.setClassName(getStyleName() + "-caption"); + + addClickHandler(this); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + sinkEvents(Event.ONMOUSEDOWN); + sinkEvents(Event.ONMOUSEUP); + } + + @Override + public void setText(String text) { + captionElement.setInnerText(text); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + if (DOM.eventGetType(event) == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + + } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN + && event.getButton() == Event.BUTTON_LEFT) { + clickPending = true; + } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) { + clickPending = false; + } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) { + if (clickPending) { + click(); + } + clickPending = false; + } + + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + + @Override + public void setWidth(String width) { + this.width = width; + super.setWidth(width); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event + * .dom.client.ClickEvent) + */ + public void onClick(ClickEvent event) { + if (paintableId == null || client == null) { + return; + } + + if (BrowserInfo.get().isSafari()) { + VNativeButton.this.setFocus(true); + } + if (disableOnClick) { + setEnabled(false); + buttonRpcProxy.disableOnClick(); + } + + // Add mouse details + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event.getNativeEvent(), getElement()); + buttonRpcProxy.click(details); + + clickPending = false; + } + + @Override + public void setEnabled(boolean enabled) { + if (isEnabled() != enabled) { + super.setEnabled(enabled); + setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativeselect/NativeSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/NativeSelectConnector.java new file mode 100644 index 0000000000..e6264e492f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/NativeSelectConnector.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.nativeselect; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.ui.NativeSelect; + +@Connect(NativeSelect.class) +public class NativeSelectConnector extends OptionGroupBaseConnector { + + @Override + protected Widget createWidget() { + return GWT.create(VNativeSelect.class); + } + + @Override + public VNativeSelect getWidget() { + return (VNativeSelect) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/VNativeSelect.java index d1b2bd76ae..54f5e9aff5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/VNativeSelect.java @@ -2,15 +2,16 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.nativeselect; import java.util.ArrayList; import java.util.Iterator; import com.google.gwt.event.dom.client.ChangeEvent; -import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.listselect.TooltipListBox; +import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroupBase; public class VNativeSelect extends VOptionGroupBase implements Field { @@ -58,11 +59,6 @@ public class VNativeSelect extends VOptionGroupBase implements Field { select.setItemSelected(0, true); firstValueIsTemporaryNullItem = true; } - if (BrowserInfo.get().isIE6()) { - // lazy size change - IE6 uses naive dropdown that does not have a - // proper size yet - Util.notifyParentOfSizeChange(this, true); - } } @Override @@ -80,10 +76,10 @@ public class VNativeSelect extends VOptionGroupBase implements Field { public void onChange(ChangeEvent event) { if (select.isMultipleSelect()) { - client.updateVariable(id, "selected", getSelectedItems(), + client.updateVariable(paintableId, "selected", getSelectedItems(), isImmediate()); } else { - client.updateVariable(id, "selected", new String[] { "" + client.updateVariable(paintableId, "selected", new String[] { "" + getSelectedItem() }, isImmediate()); } if (firstValueIsTemporaryNullItem) { @@ -113,5 +109,4 @@ public class VNativeSelect extends VOptionGroupBase implements Field { public void focus() { select.setFocus(true); } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNotification.java b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java index 27407c55aa..eb97160f52 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VNotification.java +++ b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.notification; import java.util.ArrayList; import java.util.Date; @@ -21,6 +21,8 @@ import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.root.VRoot; public class VNotification extends VOverlay { @@ -56,6 +58,13 @@ public class VNotification extends VOverlay { private ArrayList<EventListener> listeners; private static final int TOUCH_DEVICE_IDLE_DELAY = 1000; + public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style"; + public static final String ATTRIBUTE_NOTIFICATION_CAPTION = "caption"; + public static final String ATTRIBUTE_NOTIFICATION_MESSAGE = "message"; + public static final String ATTRIBUTE_NOTIFICATION_ICON = "icon"; + public static final String ATTRIBUTE_NOTIFICATION_POSITION = "position"; + public static final String ATTRIBUTE_NOTIFICATION_DELAY = "delay"; + /** * Default constructor. You should use GWT.create instead. */ @@ -357,23 +366,25 @@ public class VNotification extends VOverlay { public static void showNotification(ApplicationConnection client, final UIDL notification) { boolean onlyPlainText = notification - .hasAttribute(VView.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); + .hasAttribute(VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); String html = ""; - if (notification.hasAttribute("icon")) { + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_ICON)) { final String parsedUri = client.translateVaadinUri(notification - .getStringAttribute("icon")); + .getStringAttribute(ATTRIBUTE_NOTIFICATION_ICON)); html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />"; } - if (notification.hasAttribute("caption")) { - String caption = notification.getStringAttribute("caption"); + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_CAPTION)) { + String caption = notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_CAPTION); if (onlyPlainText) { caption = Util.escapeHTML(caption); caption = caption.replaceAll("\\n", "<br />"); } html += "<h1>" + caption + "</h1>"; } - if (notification.hasAttribute("message")) { - String message = notification.getStringAttribute("message"); + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE)) { + String message = notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE); if (onlyPlainText) { message = Util.escapeHTML(message); message = message.replaceAll("\\n", "<br />"); @@ -381,10 +392,13 @@ public class VNotification extends VOverlay { html += "<p>" + message + "</p>"; } - final String style = notification.hasAttribute("style") ? notification - .getStringAttribute("style") : null; - final int position = notification.getIntAttribute("position"); - final int delay = notification.getIntAttribute("delay"); + final String style = notification + .hasAttribute(ATTRIBUTE_NOTIFICATION_STYLE) ? notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_STYLE) : null; + final int position = notification + .getIntAttribute(ATTRIBUTE_NOTIFICATION_POSITION); + final int delay = notification + .getIntAttribute(ATTRIBUTE_NOTIFICATION_DELAY); createNotification(delay).show(html, position, style); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupBaseConnector.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupBaseConnector.java new file mode 100644 index 0000000000..3658126a97 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupBaseConnector.java @@ -0,0 +1,93 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.optiongroup; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.nativebutton.VNativeButton; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; + +public abstract class OptionGroupBaseConnector extends AbstractFieldConnector + implements Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().selectedKeys = uidl.getStringArrayVariableAsSet("selected"); + + getWidget().readonly = isReadOnly(); + getWidget().disabled = !isEnabled(); + getWidget().multiselect = "multi".equals(uidl + .getStringAttribute("selectmode")); + getWidget().immediate = getState().isImmediate(); + getWidget().nullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + getWidget().nullSelectionItemAvailable = uidl + .getBooleanAttribute("nullselectitem"); + + if (uidl.hasAttribute("cols")) { + getWidget().cols = uidl.getIntAttribute("cols"); + } + if (uidl.hasAttribute("rows")) { + getWidget().rows = uidl.getIntAttribute("rows"); + } + + final UIDL ops = uidl.getChildUIDL(0); + + if (getWidget().getColumns() > 0) { + getWidget().container.setWidth(getWidget().getColumns() + "em"); + if (getWidget().container != getWidget().optionsContainer) { + getWidget().optionsContainer.setWidth("100%"); + } + } + + getWidget().buildOptions(ops); + + if (uidl.getBooleanAttribute("allownewitem")) { + if (getWidget().newItemField == null) { + getWidget().newItemButton = new VNativeButton(); + getWidget().newItemButton.setText("+"); + getWidget().newItemButton.addClickHandler(getWidget()); + getWidget().newItemField = new VTextField(); + getWidget().newItemField.addKeyPressHandler(getWidget()); + } + getWidget().newItemField.setEnabled(!getWidget().disabled + && !getWidget().readonly); + getWidget().newItemButton.setEnabled(!getWidget().disabled + && !getWidget().readonly); + + if (getWidget().newItemField == null + || getWidget().newItemField.getParent() != getWidget().container) { + getWidget().container.add(getWidget().newItemField); + getWidget().container.add(getWidget().newItemButton); + final int w = getWidget().container.getOffsetWidth() + - getWidget().newItemButton.getOffsetWidth(); + getWidget().newItemField.setWidth(Math.max(w, 0) + "px"); + } + } else if (getWidget().newItemField != null) { + getWidget().container.remove(getWidget().newItemField); + getWidget().container.remove(getWidget().newItemButton); + } + + getWidget().setTabIndex( + uidl.hasAttribute("tabindex") ? uidl + .getIntAttribute("tabindex") : 0); + + } + + @Override + public VOptionGroupBase getWidget() { + return (VOptionGroupBase) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupConnector.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupConnector.java new file mode 100644 index 0000000000..06552a2812 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupConnector.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.optiongroup; + +import java.util.ArrayList; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.OptionGroup; + +@Connect(OptionGroup.class) +public class OptionGroupConnector extends OptionGroupBaseConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().htmlContentAllowed = uidl + .hasAttribute(VOptionGroup.HTML_CONTENT_ALLOWED); + + super.updateFromUIDL(uidl, client); + + getWidget().sendFocusEvents = client.hasEventListeners(this, + EventId.FOCUS); + getWidget().sendBlurEvents = client.hasEventListeners(this, + EventId.BLUR); + + if (getWidget().focusHandlers != null) { + for (HandlerRegistration reg : getWidget().focusHandlers) { + reg.removeHandler(); + } + getWidget().focusHandlers.clear(); + getWidget().focusHandlers = null; + + for (HandlerRegistration reg : getWidget().blurHandlers) { + reg.removeHandler(); + } + getWidget().blurHandlers.clear(); + getWidget().blurHandlers = null; + } + + if (getWidget().sendFocusEvents || getWidget().sendBlurEvents) { + getWidget().focusHandlers = new ArrayList<HandlerRegistration>(); + getWidget().blurHandlers = new ArrayList<HandlerRegistration>(); + + // add focus and blur handlers to checkboxes / radio buttons + for (Widget wid : getWidget().panel) { + if (wid instanceof CheckBox) { + getWidget().focusHandlers.add(((CheckBox) wid) + .addFocusHandler(getWidget())); + getWidget().blurHandlers.add(((CheckBox) wid) + .addBlurHandler(getWidget())); + } + } + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VOptionGroup.class); + } + + @Override + public VOptionGroup getWidget() { + return (VOptionGroup) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroup.java index 662f195fcd..d6e6949242 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroup.java @@ -2,9 +2,8 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.optiongroup; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -20,7 +19,6 @@ import com.google.gwt.event.dom.client.LoadEvent; import com.google.gwt.event.dom.client.LoadHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Focusable; @@ -28,10 +26,11 @@ import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RadioButton; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.EventId; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.checkbox.VCheckBox; public class VOptionGroup extends VOptionGroupBase implements FocusHandler, BlurHandler { @@ -40,21 +39,19 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, public static final String CLASSNAME = "v-select-optiongroup"; - private final Panel panel; + public static final String ATTRIBUTE_OPTION_DISABLED = "disabled"; + + protected final Panel panel; private final Map<CheckBox, String> optionsToKeys; - private boolean sendFocusEvents = false; - private boolean sendBlurEvents = false; - private List<HandlerRegistration> focusHandlers = null; - private List<HandlerRegistration> blurHandlers = null; + protected boolean sendFocusEvents = false; + protected boolean sendBlurEvents = false; + protected List<HandlerRegistration> focusHandlers = null; + protected List<HandlerRegistration> blurHandlers = null; private final LoadHandler iconLoadHandler = new LoadHandler() { public void onLoad(LoadEvent event) { - if (BrowserInfo.get().isIE6()) { - Util.doIE6PngFix((Element) Element.as(event.getNativeEvent() - .getEventTarget())); - } Util.notifyParentOfSizeChange(VOptionGroup.this, true); } }; @@ -68,7 +65,7 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, */ private boolean blurOccured = false; - private boolean htmlContentAllowed = false; + protected boolean htmlContentAllowed = false; public VOptionGroup() { super(CLASSNAME); @@ -76,43 +73,6 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, optionsToKeys = new HashMap<CheckBox, String>(); } - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - htmlContentAllowed = uidl.hasAttribute(HTML_CONTENT_ALLOWED); - - super.updateFromUIDL(uidl, client); - - sendFocusEvents = client.hasEventListeners(this, EventId.FOCUS); - sendBlurEvents = client.hasEventListeners(this, EventId.BLUR); - - if (focusHandlers != null) { - for (HandlerRegistration reg : focusHandlers) { - reg.removeHandler(); - } - focusHandlers.clear(); - focusHandlers = null; - - for (HandlerRegistration reg : blurHandlers) { - reg.removeHandler(); - } - blurHandlers.clear(); - blurHandlers = null; - } - - if (sendFocusEvents || sendBlurEvents) { - focusHandlers = new ArrayList<HandlerRegistration>(); - blurHandlers = new ArrayList<HandlerRegistration>(); - - // add focus and blur handlers to checkboxes / radio buttons - for (Widget wid : panel) { - if (wid instanceof CheckBox) { - focusHandlers.add(((CheckBox) wid).addFocusHandler(this)); - blurHandlers.add(((CheckBox) wid).addBlurHandler(this)); - } - } - } - } - /* * Return true if no elements were changed, false otherwise. */ @@ -139,7 +99,7 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, op = new VCheckBox(); op.setHTML(itemHtml); } else { - op = new RadioButton(id, itemHtml, true); + op = new RadioButton(paintableId, itemHtml, true); op.setStyleName("v-radiobutton"); } @@ -150,7 +110,8 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, op.addStyleName(CLASSNAME_OPTION); op.setValue(opUidl.getBooleanAttribute("selected")); - boolean enabled = !opUidl.getBooleanAttribute("disabled") + boolean enabled = !opUidl + .getBooleanAttribute(ATTRIBUTE_OPTION_DISABLED) && !isReadonly() && !isDisabled(); op.setEnabled(enabled); setStyleName(op.getElement(), @@ -180,7 +141,7 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, } else { selectedKeys.remove(key); } - client.updateVariable(id, "selected", getSelectedItems(), + client.updateVariable(paintableId, "selected", getSelectedItems(), isImmediate()); } } @@ -206,7 +167,7 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, // panel was blurred => fire the event to the server side if // requested by server side if (sendFocusEvents) { - client.updateVariable(id, EventId.FOCUS, "", true); + client.updateVariable(paintableId, EventId.FOCUS, "", true); } } else { // blur occured before this focus event @@ -225,7 +186,8 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, // check whether blurOccured still is true and then send the // event out to the server if (blurOccured) { - client.updateVariable(id, EventId.BLUR, "", true); + client.updateVariable(paintableId, EventId.BLUR, "", + true); blurOccured = false; } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBase.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroupBase.java index 50a9f8cb98..a512f024b8 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBase.java +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroupBase.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.optiongroup; import java.util.Set; @@ -19,35 +19,37 @@ import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.Paintable; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.nativebutton.VNativeButton; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; -abstract class VOptionGroupBase extends Composite implements Paintable, Field, +public abstract class VOptionGroupBase extends Composite implements Field, ClickHandler, ChangeHandler, KeyPressHandler, Focusable { public static final String CLASSNAME_OPTION = "v-select-option"; protected ApplicationConnection client; - protected String id; + protected String paintableId; protected Set<String> selectedKeys; - private boolean immediate; + protected boolean immediate; - private boolean multiselect; + protected boolean multiselect; - private boolean disabled; + protected boolean disabled; - private boolean readonly; + protected boolean readonly; - private int cols = 0; + protected int cols = 0; - private int rows = 0; + protected int rows = 0; - private boolean nullSelectionAllowed = true; + protected boolean nullSelectionAllowed = true; - private boolean nullSelectionItemAvailable = false; + protected boolean nullSelectionItemAvailable = false; /** * Widget holding the different options (e.g. ListBox or Panel for radio @@ -58,11 +60,11 @@ abstract class VOptionGroupBase extends Composite implements Paintable, Field, /** * Panel containing the component */ - private final Panel container; + protected final Panel container; - private VTextField newItemField; + protected VTextField newItemField; - private VNativeButton newItemButton; + protected VNativeButton newItemButton; public VOptionGroupBase(String classname) { container = new FlowPanel(); @@ -122,84 +124,23 @@ abstract class VOptionGroupBase extends Composite implements Paintable, Field, return rows; } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - id = uidl.getId(); - - if (client.updateComponent(this, uidl, true)) { - return; - } - - selectedKeys = uidl.getStringArrayVariableAsSet("selected"); - - readonly = uidl.getBooleanAttribute("readonly"); - disabled = uidl.getBooleanAttribute("disabled"); - multiselect = "multi".equals(uidl.getStringAttribute("selectmode")); - immediate = uidl.getBooleanAttribute("immediate"); - nullSelectionAllowed = uidl.getBooleanAttribute("nullselect"); - nullSelectionItemAvailable = uidl.getBooleanAttribute("nullselectitem"); - - if (uidl.hasAttribute("cols")) { - cols = uidl.getIntAttribute("cols"); - } - if (uidl.hasAttribute("rows")) { - rows = uidl.getIntAttribute("rows"); - } - - final UIDL ops = uidl.getChildUIDL(0); - - if (getColumns() > 0) { - container.setWidth(getColumns() + "em"); - if (container != optionsContainer) { - optionsContainer.setWidth("100%"); - } - } - - buildOptions(ops); - - if (uidl.getBooleanAttribute("allownewitem")) { - if (newItemField == null) { - newItemButton = new VNativeButton(); - newItemButton.setText("+"); - newItemButton.addClickHandler(this); - newItemField = new VTextField(); - newItemField.addKeyPressHandler(this); - } - newItemField.setEnabled(!disabled && !readonly); - newItemButton.setEnabled(!disabled && !readonly); - - if (newItemField == null || newItemField.getParent() != container) { - container.add(newItemField); - container.add(newItemButton); - final int w = container.getOffsetWidth() - - newItemButton.getOffsetWidth(); - newItemField.setWidth(Math.max(w, 0) + "px"); - } - } else if (newItemField != null) { - container.remove(newItemField); - container.remove(newItemButton); - } - - setTabIndex(uidl.hasAttribute("tabindex") ? uidl - .getIntAttribute("tabindex") : 0); - - } - abstract protected void setTabIndex(int tabIndex); public void onClick(ClickEvent event) { if (event.getSource() == newItemButton && !newItemField.getText().equals("")) { - client.updateVariable(id, "newitem", newItemField.getText(), true); + client.updateVariable(paintableId, "newitem", + newItemField.getText(), true); newItemField.setText(""); } } public void onChange(ChangeEvent event) { if (multiselect) { - client.updateVariable(id, "selected", getSelectedItems(), immediate); + client.updateVariable(paintableId, "selected", getSelectedItems(), + immediate); } else { - client.updateVariable(id, "selected", new String[] { "" + client.updateVariable(paintableId, "selected", new String[] { "" + getSelectedItem() }, immediate); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java new file mode 100644 index 0000000000..174da61bd3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java @@ -0,0 +1,331 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import java.util.List; + +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ValueMap; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; +import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.layout.ComponentConnectorLayoutSlot; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; + +public abstract class AbstractOrderedLayoutConnector extends + AbstractLayoutConnector implements Paintable, DirectionalManagedLayout { + + AbstractOrderedLayoutServerRpc rpc; + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return Util.getConnectorForElement(getConnection(), getWidget(), + element); + } + + @Override + protected LayoutClickRpc getLayoutClickRPC() { + return rpc; + }; + + }; + + @Override + public void init() { + rpc = RpcProxy.create(AbstractOrderedLayoutServerRpc.class, this); + getLayoutManager().registerDependency(this, + getWidget().spacingMeasureElement); + } + + @Override + public void onUnregister() { + LayoutManager lm = getLayoutManager(); + + VMeasuringOrderedLayout layout = getWidget(); + lm.unregisterDependency(this, layout.spacingMeasureElement); + + // Unregister child caption listeners + for (ComponentConnector child : getChildren()) { + VLayoutSlot slot = layout.getSlotForChild(child.getWidget()); + slot.setCaption(null); + } + } + + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } + + public void updateCaption(ComponentConnector component) { + VMeasuringOrderedLayout layout = getWidget(); + if (VCaption.isNeeded(component.getState())) { + VLayoutSlot layoutSlot = layout.getSlotForChild(component + .getWidget()); + VCaption caption = layoutSlot.getCaption(); + if (caption == null) { + caption = new VCaption(component, getConnection()); + + Widget widget = component.getWidget(); + + layout.setCaption(widget, caption); + } + caption.updateCaption(); + } else { + layout.setCaption(component.getWidget(), null); + getLayoutManager().setNeedsLayout(this); + } + } + + @Override + public VMeasuringOrderedLayout getWidget() { + return (VMeasuringOrderedLayout) super.getWidget(); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + clickEventHandler.handleEventHandlerRegistration(); + + VMeasuringOrderedLayout layout = getWidget(); + + ValueMap expandRatios = uidl.getMapAttribute("expandRatios"); + ValueMap alignments = uidl.getMapAttribute("alignments"); + + for (ComponentConnector child : getChildren()) { + VLayoutSlot slot = layout.getSlotForChild(child.getWidget()); + String pid = child.getConnectorId(); + + AlignmentInfo alignment; + if (alignments.containsKey(pid)) { + alignment = new AlignmentInfo(alignments.getInt(pid)); + } else { + alignment = AlignmentInfo.TOP_LEFT; + } + slot.setAlignment(alignment); + + double expandRatio; + if (expandRatios.containsKey(pid)) { + expandRatio = expandRatios.getRawNumber(pid); + } else { + expandRatio = 0; + } + slot.setExpandRatio(expandRatio); + } + + layout.updateMarginStyleNames(new VMarginInfo(getState() + .getMarginsBitmask())); + + layout.updateSpacingStyleName(getState().isSpacing()); + + getLayoutManager().setNeedsLayout(this); + } + + private int getSizeForInnerSize(int size, boolean isVertical) { + LayoutManager layoutManager = getLayoutManager(); + Element element = getWidget().getElement(); + if (isVertical) { + return size + layoutManager.getBorderHeight(element) + + layoutManager.getPaddingHeight(element); + } else { + return size + layoutManager.getBorderWidth(element) + + layoutManager.getPaddingWidth(element); + } + } + + private static String getSizeProperty(boolean isVertical) { + return isVertical ? "height" : "width"; + } + + private boolean isUndefinedInDirection(boolean isVertical) { + if (isVertical) { + return isUndefinedHeight(); + } else { + return isUndefinedWidth(); + } + } + + private int getInnerSizeInDirection(boolean isVertical) { + if (isVertical) { + return getLayoutManager().getInnerHeight(getWidget().getElement()); + } else { + return getLayoutManager().getInnerWidth(getWidget().getElement()); + } + } + + private void layoutPrimaryDirection() { + VMeasuringOrderedLayout layout = getWidget(); + boolean isVertical = layout.isVertical; + boolean isUndefined = isUndefinedInDirection(isVertical); + + int startPadding = getStartPadding(isVertical); + int endPadding = getEndPadding(isVertical); + int spacingSize = getSpacingInDirection(isVertical); + int allocatedSize; + + if (isUndefined) { + allocatedSize = -1; + } else { + allocatedSize = getInnerSizeInDirection(isVertical); + } + + allocatedSize = layout.layoutPrimaryDirection(spacingSize, + allocatedSize, startPadding, endPadding); + + Style ownStyle = getWidget().getElement().getStyle(); + if (isUndefined) { + int outerSize = getSizeForInnerSize(allocatedSize, isVertical); + ownStyle.setPropertyPx(getSizeProperty(isVertical), outerSize); + reportUndefinedSize(outerSize, isVertical); + } else { + ownStyle.setProperty(getSizeProperty(isVertical), + getDefinedSize(isVertical)); + } + } + + private void reportUndefinedSize(int outerSize, boolean isVertical) { + if (isVertical) { + getLayoutManager().reportOuterHeight(this, outerSize); + } else { + getLayoutManager().reportOuterWidth(this, outerSize); + } + } + + private int getSpacingInDirection(boolean isVertical) { + if (isVertical) { + return getLayoutManager().getOuterHeight( + getWidget().spacingMeasureElement); + } else { + return getLayoutManager().getOuterWidth( + getWidget().spacingMeasureElement); + } + } + + private void layoutSecondaryDirection() { + VMeasuringOrderedLayout layout = getWidget(); + boolean isVertical = layout.isVertical; + boolean isUndefined = isUndefinedInDirection(!isVertical); + + int startPadding = getStartPadding(!isVertical); + int endPadding = getEndPadding(!isVertical); + + int allocatedSize; + if (isUndefined) { + allocatedSize = -1; + } else { + allocatedSize = getInnerSizeInDirection(!isVertical); + } + + allocatedSize = layout.layoutSecondaryDirection(allocatedSize, + startPadding, endPadding); + + Style ownStyle = getWidget().getElement().getStyle(); + + if (isUndefined) { + int outerSize = getSizeForInnerSize(allocatedSize, + !getWidget().isVertical); + ownStyle.setPropertyPx(getSizeProperty(!getWidget().isVertical), + outerSize); + reportUndefinedSize(outerSize, !isVertical); + } else { + ownStyle.setProperty(getSizeProperty(!getWidget().isVertical), + getDefinedSize(!getWidget().isVertical)); + } + } + + private String getDefinedSize(boolean isVertical) { + if (isVertical) { + return getState().getHeight(); + } else { + return getState().getWidth(); + } + } + + private int getStartPadding(boolean isVertical) { + if (isVertical) { + return getLayoutManager().getPaddingTop(getWidget().getElement()); + } else { + return getLayoutManager().getPaddingLeft(getWidget().getElement()); + } + } + + private int getEndPadding(boolean isVertical) { + if (isVertical) { + return getLayoutManager() + .getPaddingBottom(getWidget().getElement()); + } else { + return getLayoutManager().getPaddingRight(getWidget().getElement()); + } + } + + public void layoutHorizontally() { + if (getWidget().isVertical) { + layoutSecondaryDirection(); + } else { + layoutPrimaryDirection(); + } + } + + public void layoutVertically() { + if (getWidget().isVertical) { + layoutPrimaryDirection(); + } else { + layoutSecondaryDirection(); + } + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + List<ComponentConnector> previousChildren = event.getOldChildren(); + int currentIndex = 0; + VMeasuringOrderedLayout layout = getWidget(); + + for (ComponentConnector child : getChildren()) { + Widget childWidget = child.getWidget(); + VLayoutSlot slot = layout.getSlotForChild(childWidget); + + if (childWidget.getParent() != layout) { + // If the child widget was previously attached to another + // AbstractOrderedLayout a slot might be found that belongs to + // another AbstractOrderedLayout. In this case we discard it and + // create a new slot. + slot = new ComponentConnectorLayoutSlot(getWidget() + .getStylePrimaryName(), child, this); + } + layout.addOrMove(slot, currentIndex++); + if (child.isRelativeWidth()) { + slot.getWrapperElement().getStyle().setWidth(100, Unit.PCT); + } + } + + for (ComponentConnector child : previousChildren) { + if (child.getParent() != this) { + // Remove slot if the connector is no longer a child of this + // layout + layout.removeSlotForWidget(child.getWidget()); + } + } + + }; + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutServerRpc.java new file mode 100644 index 0000000000..5a29eacada --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutServerRpc.java @@ -0,0 +1,12 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; + +public interface AbstractOrderedLayoutServerRpc extends LayoutClickRpc, + ServerRpc { + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutState.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutState.java new file mode 100644 index 0000000000..bf542d3951 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutState.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutState; + +public class AbstractOrderedLayoutState extends AbstractLayoutState { + private boolean spacing = false; + + public boolean isSpacing() { + return spacing; + } + + public void setSpacing(boolean spacing) { + this.spacing = spacing; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java new file mode 100644 index 0000000000..a12b41ade7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java @@ -0,0 +1,24 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.HorizontalLayout; + +@Connect(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) +public class HorizontalLayoutConnector extends AbstractOrderedLayoutConnector { + + @Override + public VHorizontalLayout getWidget() { + return (VHorizontalLayout) super.getWidget(); + } + + @Override + protected VHorizontalLayout createWidget() { + return GWT.create(VHorizontalLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VHorizontalLayout.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VHorizontalLayout.java new file mode 100644 index 0000000000..5bf377642e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VHorizontalLayout.java @@ -0,0 +1,14 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +public class VHorizontalLayout extends VMeasuringOrderedLayout { + + public static final String CLASSNAME = "v-horizontallayout"; + + public VHorizontalLayout() { + super(CLASSNAME, false); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VMeasuringOrderedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VMeasuringOrderedLayout.java new file mode 100644 index 0000000000..de55ca98e6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VMeasuringOrderedLayout.java @@ -0,0 +1,241 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.WidgetCollection; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; + +public class VMeasuringOrderedLayout extends ComplexPanel { + + final boolean isVertical; + + final DivElement spacingMeasureElement; + + private Map<Widget, VLayoutSlot> widgetToSlot = new HashMap<Widget, VLayoutSlot>(); + + protected VMeasuringOrderedLayout(String className, boolean isVertical) { + DivElement element = Document.get().createDivElement(); + setElement(element); + + spacingMeasureElement = Document.get().createDivElement(); + Style spacingStyle = spacingMeasureElement.getStyle(); + spacingStyle.setPosition(Position.ABSOLUTE); + getElement().appendChild(spacingMeasureElement); + + setStyleName(className); + this.isVertical = isVertical; + } + + public void addOrMove(VLayoutSlot layoutSlot, int index) { + Widget widget = layoutSlot.getWidget(); + Element wrapperElement = layoutSlot.getWrapperElement(); + + Element containerElement = getElement(); + Node childAtIndex = containerElement.getChild(index); + if (childAtIndex != wrapperElement) { + // Insert at correct location not attached or at wrong location + containerElement.insertBefore(wrapperElement, childAtIndex); + insert(widget, wrapperElement, index, false); + } + + widgetToSlot.put(widget, layoutSlot); + } + + private void togglePrefixedStyleName(String name, boolean enabled) { + if (enabled) { + addStyleDependentName(name); + } else { + removeStyleDependentName(name); + } + } + + void updateMarginStyleNames(VMarginInfo marginInfo) { + togglePrefixedStyleName("margin-top", marginInfo.hasTop()); + togglePrefixedStyleName("margin-right", marginInfo.hasRight()); + togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); + togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); + } + + void updateSpacingStyleName(boolean spacingEnabled) { + String styleName = getStylePrimaryName(); + if (spacingEnabled) { + spacingMeasureElement.addClassName(styleName + "-spacing-on"); + spacingMeasureElement.removeClassName(styleName + "-spacing-off"); + } else { + spacingMeasureElement.removeClassName(styleName + "-spacing-on"); + spacingMeasureElement.addClassName(styleName + "-spacing-off"); + } + } + + public void removeSlotForWidget(Widget widget) { + VLayoutSlot slot = getSlotForChild(widget); + VCaption caption = slot.getCaption(); + if (caption != null) { + // Must remove using setCaption to ensure dependencies (layout -> + // caption) are unregistered + slot.setCaption(null); + } + + remove(slot.getWidget()); + getElement().removeChild(slot.getWrapperElement()); + widgetToSlot.remove(widget); + } + + public VLayoutSlot getSlotForChild(Widget widget) { + return widgetToSlot.get(widget); + } + + public void setCaption(Widget child, VCaption caption) { + VLayoutSlot slot = getSlotForChild(child); + + if (caption != null) { + // Logical attach. + getChildren().add(caption); + } + + // Physical attach if not null, also removes old caption + slot.setCaption(caption); + + if (caption != null) { + // Adopt. + adopt(caption); + } + } + + public int layoutPrimaryDirection(int spacingSize, int allocatedSize, + int startPadding, int endPadding) { + int actuallyAllocated = 0; + double totalExpand = 0; + + int childCount = 0; + for (Widget child : this) { + if (child instanceof VCaption) { + continue; + } + childCount++; + + VLayoutSlot slot = getSlotForChild(child); + totalExpand += slot.getExpandRatio(); + + if (!slot.isRelativeInDirection(isVertical)) { + actuallyAllocated += slot.getUsedSizeInDirection(isVertical); + } + } + + actuallyAllocated += spacingSize * (childCount - 1); + + if (allocatedSize == -1) { + allocatedSize = actuallyAllocated; + } + + double unallocatedSpace = Math + .max(0, allocatedSize - actuallyAllocated); + + double currentLocation = startPadding; + + WidgetCollection children = getChildren(); + for (int i = 0; i < children.size(); i++) { + Widget child = children.get(i); + if (child instanceof VCaption) { + continue; + } + + VLayoutSlot slot = getSlotForChild(child); + + double childExpandRatio; + if (totalExpand == 0) { + childExpandRatio = 1d / childCount; + } else { + childExpandRatio = slot.getExpandRatio() / totalExpand; + } + + double extraPixels = unallocatedSpace * childExpandRatio; + double endLocation = currentLocation + extraPixels; + if (!slot.isRelativeInDirection(isVertical)) { + endLocation += slot.getUsedSizeInDirection(isVertical); + } + + /* + * currentLocation and allocatedSpace are used with full precision + * to avoid missing pixels in the end. The pixel dimensions passed + * to the DOM are still rounded. Otherwise e.g. 10.5px start + * position + 10.5px space might be cause the component to go 1px + * beyond the edge as the effect of the browser's rounding may cause + * something similar to 11px + 11px. + * + * It's most efficient to use doubles all the way because native + * javascript emulates other number types using doubles. + */ + double roundedLocation = Math.round(currentLocation); + + /* + * Space is calculated as the difference between rounded start and + * end locations. Just rounding the space would cause e.g. 10.5px + + * 10.5px = 21px -> 11px + 11px = 22px but in this way we get 11px + + * 10px = 21px. + */ + double roundedSpace = Math.round(endLocation) - roundedLocation; + + // Reserve room for the padding if we're at the end + double slotEndMargin; + if (i == children.size() - 1) { + slotEndMargin = endPadding; + } else { + slotEndMargin = 0; + } + + slot.positionInDirection(roundedLocation, roundedSpace, + slotEndMargin, isVertical); + + currentLocation = endLocation + spacingSize; + } + + return allocatedSize; + } + + public int layoutSecondaryDirection(int allocatedSize, int startPadding, + int endPadding) { + int maxSize = 0; + for (Widget child : this) { + if (child instanceof VCaption) { + continue; + } + + VLayoutSlot slot = getSlotForChild(child); + if (!slot.isRelativeInDirection(!isVertical)) { + maxSize = Math.max(maxSize, + slot.getUsedSizeInDirection(!isVertical)); + } + } + + if (allocatedSize == -1) { + allocatedSize = maxSize; + } + + for (Widget child : this) { + if (child instanceof VCaption) { + continue; + } + + VLayoutSlot slot = getSlotForChild(child); + slot.positionInDirection(startPadding, allocatedSize, endPadding, + !isVertical); + } + + return allocatedSize; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VVerticalLayout.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VVerticalLayout.java new file mode 100644 index 0000000000..e44c576941 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VVerticalLayout.java @@ -0,0 +1,14 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +public class VVerticalLayout extends VMeasuringOrderedLayout { + + public static final String CLASSNAME = "v-verticallayout"; + + public VVerticalLayout() { + super(CLASSNAME, true); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java new file mode 100644 index 0000000000..1e5651ce38 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java @@ -0,0 +1,24 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.VerticalLayout; + +@Connect(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) +public class VerticalLayoutConnector extends AbstractOrderedLayoutConnector { + + @Override + public VVerticalLayout getWidget() { + return (VVerticalLayout) super.getWidget(); + } + + @Override + protected VVerticalLayout createWidget() { + return GWT.create(VVerticalLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java new file mode 100644 index 0000000000..9555c38a36 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java @@ -0,0 +1,243 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.panel; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.layout.MayScrollChildren; +import com.vaadin.ui.Panel; + +@Connect(Panel.class) +public class PanelConnector extends AbstractComponentContainerConnector + implements Paintable, SimpleManagedLayout, PostLayoutListener, + MayScrollChildren { + + private Integer uidlScrollTop; + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.click(mouseDetails); + } + }; + + private Integer uidlScrollLeft; + + private PanelServerRpc rpc; + + @Override + public void init() { + rpc = RpcProxy.create(PanelServerRpc.class, this); + VPanel panel = getWidget(); + LayoutManager layoutManager = getLayoutManager(); + + layoutManager.registerDependency(this, panel.captionNode); + layoutManager.registerDependency(this, panel.bottomDecoration); + layoutManager.registerDependency(this, panel.contentNode); + } + + @Override + public void onUnregister() { + VPanel panel = getWidget(); + LayoutManager layoutManager = getLayoutManager(); + + layoutManager.unregisterDependency(this, panel.captionNode); + layoutManager.unregisterDependency(this, panel.bottomDecoration); + layoutManager.unregisterDependency(this, panel.contentNode); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (isRealUpdate(uidl)) { + + // Handle caption displaying and style names, prior generics. + // Affects size calculations + + // Restore default stylenames + getWidget().contentNode.setClassName(VPanel.CLASSNAME + "-content"); + getWidget().bottomDecoration.setClassName(VPanel.CLASSNAME + + "-deco"); + getWidget().captionNode.setClassName(VPanel.CLASSNAME + "-caption"); + boolean hasCaption = false; + if (getState().getCaption() != null + && !"".equals(getState().getCaption())) { + getWidget().setCaption(getState().getCaption()); + hasCaption = true; + } else { + getWidget().setCaption(""); + getWidget().captionNode.setClassName(VPanel.CLASSNAME + + "-nocaption"); + } + + // Add proper stylenames for all elements. This way we can prevent + // unwanted CSS selector inheritance. + final String captionBaseClass = VPanel.CLASSNAME + + (hasCaption ? "-caption" : "-nocaption"); + final String contentBaseClass = VPanel.CLASSNAME + "-content"; + final String decoBaseClass = VPanel.CLASSNAME + "-deco"; + String captionClass = captionBaseClass; + String contentClass = contentBaseClass; + String decoClass = decoBaseClass; + if (getState().hasStyles()) { + for (String style : getState().getStyles()) { + captionClass += " " + captionBaseClass + "-" + style; + contentClass += " " + contentBaseClass + "-" + style; + decoClass += " " + decoBaseClass + "-" + style; + } + } + getWidget().captionNode.setClassName(captionClass); + getWidget().contentNode.setClassName(contentClass); + getWidget().bottomDecoration.setClassName(decoClass); + } + + if (!isRealUpdate(uidl)) { + return; + } + + clickEventHandler.handleEventHandlerRegistration(); + + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (getState().getIcon() != null) { + getWidget().setIconUri(getState().getIcon().getURL(), client); + } else { + getWidget().setIconUri(null, client); + } + + getWidget().setErrorIndicatorVisible( + null != getState().getErrorMessage()); + + // We may have actions attached to this panel + if (uidl.getChildCount() > 0) { + final int cnt = uidl.getChildCount(); + for (int i = 0; i < cnt; i++) { + UIDL childUidl = uidl.getChildUIDL(i); + if (childUidl.getTag().equals("actions")) { + if (getWidget().shortcutHandler == null) { + getWidget().shortcutHandler = new ShortcutActionHandler( + getConnectorId(), client); + } + getWidget().shortcutHandler.updateActionMap(childUidl); + } + } + } + + if (getState().getScrollTop() != getWidget().scrollTop) { + // Sizes are not yet up to date, so changing the scroll position + // is deferred to after the layout phase + uidlScrollTop = getState().getScrollTop(); + } + + if (getState().getScrollLeft() != getWidget().scrollLeft) { + // Sizes are not yet up to date, so changing the scroll position + // is deferred to after the layout phase + uidlScrollLeft = getState().getScrollLeft(); + } + + // And apply tab index + getWidget().contentNode.setTabIndex(getState().getTabIndex()); + } + + public void updateCaption(ComponentConnector component) { + // NOP: layouts caption, errors etc not rendered in Panel + } + + @Override + public VPanel getWidget() { + return (VPanel) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPanel.class); + } + + public void layout() { + updateSizes(); + } + + void updateSizes() { + VPanel panel = getWidget(); + + LayoutManager layoutManager = getLayoutManager(); + int top = layoutManager.getOuterHeight(panel.captionNode); + int bottom = layoutManager.getInnerHeight(panel.bottomDecoration); + + Style style = panel.getElement().getStyle(); + panel.captionNode.getParentElement().getStyle() + .setMarginTop(-top, Unit.PX); + panel.bottomDecoration.getStyle().setMarginBottom(-bottom, Unit.PX); + style.setPaddingTop(top, Unit.PX); + style.setPaddingBottom(bottom, Unit.PX); + + // Update scroll positions + panel.contentNode.setScrollTop(panel.scrollTop); + panel.contentNode.setScrollLeft(panel.scrollLeft); + // Read actual value back to ensure update logic is correct + panel.scrollTop = panel.contentNode.getScrollTop(); + panel.scrollLeft = panel.contentNode.getScrollLeft(); + } + + public void postLayout() { + VPanel panel = getWidget(); + if (uidlScrollTop != null) { + panel.contentNode.setScrollTop(uidlScrollTop.intValue()); + // Read actual value back to ensure update logic is correct + // TODO Does this trigger reflows? + panel.scrollTop = panel.contentNode.getScrollTop(); + uidlScrollTop = null; + } + + if (uidlScrollLeft != null) { + panel.contentNode.setScrollLeft(uidlScrollLeft.intValue()); + // Read actual value back to ensure update logic is correct + // TODO Does this trigger reflows? + panel.scrollLeft = panel.contentNode.getScrollLeft(); + uidlScrollLeft = null; + } + } + + @Override + public PanelState getState() { + return (PanelState) super.getState(); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + // We always have 1 child, unless the child is hidden + Widget newChildWidget = null; + if (getChildren().size() == 1) { + ComponentConnector newChild = getChildren().get(0); + newChildWidget = newChild.getWidget(); + } + + getWidget().setWidget(newChildWidget); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelServerRpc.java new file mode 100644 index 0000000000..9b59344aec --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelServerRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.panel; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; + +public interface PanelServerRpc extends ClickRpc, ServerRpc { + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelState.java b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelState.java new file mode 100644 index 0000000000..fc7921825f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelState.java @@ -0,0 +1,36 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.panel; + +import com.vaadin.terminal.gwt.client.ComponentState; + +public class PanelState extends ComponentState { + private int tabIndex; + private int scrollLeft, scrollTop; + + public int getTabIndex() { + return tabIndex; + } + + public void setTabIndex(int tabIndex) { + this.tabIndex = tabIndex; + } + + public int getScrollLeft() { + return scrollLeft; + } + + public void setScrollLeft(int scrollLeft) { + this.scrollLeft = scrollLeft; + } + + public int getScrollTop() { + return scrollTop; + } + + public void setScrollTop(int scrollTop) { + this.scrollTop = scrollTop; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java new file mode 100644 index 0000000000..e2d3d443a0 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java @@ -0,0 +1,189 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.panel; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.SimplePanel; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; + +public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, + Focusable { + + public static final String CLASSNAME = "v-panel"; + + ApplicationConnection client; + + String id; + + final Element captionNode = DOM.createDiv(); + + private final Element captionText = DOM.createSpan(); + + private Icon icon; + + final Element bottomDecoration = DOM.createDiv(); + + final Element contentNode = DOM.createDiv(); + + private Element errorIndicatorElement; + + ShortcutActionHandler shortcutHandler; + + int scrollTop; + + int scrollLeft; + + private TouchScrollDelegate touchScrollDelegate; + + public VPanel() { + super(); + DivElement captionWrap = Document.get().createDivElement(); + captionWrap.appendChild(captionNode); + captionNode.appendChild(captionText); + + captionWrap.setClassName(CLASSNAME + "-captionwrap"); + captionNode.setClassName(CLASSNAME + "-caption"); + contentNode.setClassName(CLASSNAME + "-content"); + bottomDecoration.setClassName(CLASSNAME + "-deco"); + + getElement().appendChild(captionWrap); + + /* + * Make contentNode focusable only by using the setFocus() method. This + * behaviour can be changed by invoking setTabIndex() in the serverside + * implementation + */ + contentNode.setTabIndex(-1); + + getElement().appendChild(contentNode); + + getElement().appendChild(bottomDecoration); + setStyleName(CLASSNAME); + DOM.sinkEvents(getElement(), Event.ONKEYDOWN); + DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); + addHandler(new TouchStartHandler() { + public void onTouchStart(TouchStartEvent event) { + getTouchScrollDelegate().onTouchStart(event); + } + }, TouchStartEvent.getType()); + } + + /** + * Sets the keyboard focus on the Panel + * + * @param focus + * Should the panel have focus or not. + */ + public void setFocus(boolean focus) { + if (focus) { + getContainerElement().focus(); + } else { + getContainerElement().blur(); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Focusable#focus() + */ + public void focus() { + setFocus(true); + + } + + @Override + protected Element getContainerElement() { + return contentNode; + } + + void setCaption(String text) { + DOM.setInnerHTML(captionText, text); + } + + void setErrorIndicatorVisible(boolean showError) { + if (showError) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createSpan(); + DOM.setElementProperty(errorIndicatorElement, "className", + "v-errorindicator"); + DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); + sinkEvents(Event.MOUSEEVENTS); + } + DOM.insertBefore(captionNode, errorIndicatorElement, captionText); + } else if (errorIndicatorElement != null) { + DOM.removeChild(captionNode, errorIndicatorElement); + errorIndicatorElement = null; + } + } + + void setIconUri(String iconUri, ApplicationConnection client) { + if (iconUri == null) { + if (icon != null) { + DOM.removeChild(captionNode, icon.getElement()); + icon = null; + } + } else { + if (icon == null) { + icon = new Icon(client); + DOM.insertChild(captionNode, icon.getElement(), 0); + } + icon.setUri(iconUri); + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + final Element target = DOM.eventGetTarget(event); + final int type = DOM.eventGetType(event); + if (type == Event.ONKEYDOWN && shortcutHandler != null) { + shortcutHandler.handleKeyboardEvent(event); + return; + } + if (type == Event.ONSCROLL) { + int newscrollTop = DOM.getElementPropertyInt(contentNode, + "scrollTop"); + int newscrollLeft = DOM.getElementPropertyInt(contentNode, + "scrollLeft"); + if (client != null + && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) { + scrollLeft = newscrollLeft; + scrollTop = newscrollTop; + client.updateVariable(id, "scrollTop", scrollTop, false); + client.updateVariable(id, "scrollLeft", scrollLeft, false); + } + } else if (captionNode.isOrHasChild(target)) { + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + } + + protected TouchScrollDelegate getTouchScrollDelegate() { + if (touchScrollDelegate == null) { + touchScrollDelegate = new TouchScrollDelegate(contentNode); + } + return touchScrollDelegate; + + } + + public ShortcutActionHandler getShortcutActionHandler() { + return shortcutHandler; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/passwordfield/PasswordFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/PasswordFieldConnector.java new file mode 100644 index 0000000000..1d1ec58fbb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/PasswordFieldConnector.java @@ -0,0 +1,32 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.passwordfield; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.textfield.TextFieldConnector; +import com.vaadin.ui.PasswordField; + +@Connect(PasswordField.class) +public class PasswordFieldConnector extends TextFieldConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPasswordField.class); + } + + @Override + public VPasswordField getWidget() { + return (VPasswordField) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPasswordField.java b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/VPasswordField.java index 5182457067..c160322de5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VPasswordField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/VPasswordField.java @@ -2,9 +2,10 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.passwordfield; import com.google.gwt.user.client.DOM; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; /** * This class represents a password field. diff --git a/src/com/vaadin/terminal/gwt/client/ui/popupview/PopupViewConnector.java b/src/com/vaadin/terminal/gwt/client/ui/popupview/PopupViewConnector.java new file mode 100644 index 0000000000..4faa6cec86 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/popupview/PopupViewConnector.java @@ -0,0 +1,121 @@ +/* + @VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.popupview; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.VCaptionWrapper; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.ui.PopupView; + +@Connect(PopupView.class) +public class PopupViewConnector extends AbstractComponentContainerConnector + implements Paintable, PostLayoutListener { + + private boolean centerAfterLayout = false; + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + /** + * + * + * @see com.vaadin.terminal.gwt.client.ComponentConnector#updateFromUIDL(com.vaadin.terminal.gwt.client.UIDL, + * com.vaadin.terminal.gwt.client.ApplicationConnection) + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + // These are for future server connections + getWidget().client = client; + getWidget().uidlId = uidl.getId(); + + getWidget().hostPopupVisible = uidl + .getBooleanVariable("popupVisibility"); + + getWidget().setHTML(uidl.getStringAttribute("html")); + + if (uidl.hasAttribute("hideOnMouseOut")) { + getWidget().popup.setHideOnMouseOut(uidl + .getBooleanAttribute("hideOnMouseOut")); + } + + // Render the popup if visible and show it. + if (getWidget().hostPopupVisible) { + UIDL popupUIDL = uidl.getChildUIDL(0); + + // showPopupOnTop(popup, hostReference); + getWidget().preparePopup(getWidget().popup); + getWidget().popup.updateFromUIDL(popupUIDL, client); + if (getState().hasStyles()) { + final StringBuffer styleBuf = new StringBuffer(); + final String primaryName = getWidget().popup + .getStylePrimaryName(); + styleBuf.append(primaryName); + for (String style : getState().getStyles()) { + styleBuf.append(" "); + styleBuf.append(primaryName); + styleBuf.append("-"); + styleBuf.append(style); + } + getWidget().popup.setStyleName(styleBuf.toString()); + } else { + getWidget().popup.setStyleName(getWidget().popup + .getStylePrimaryName()); + } + getWidget().showPopup(getWidget().popup); + centerAfterLayout = true; + + // The popup shouldn't be visible, try to hide it. + } else { + getWidget().popup.hide(); + } + }// updateFromUIDL + + public void updateCaption(ComponentConnector component) { + if (VCaption.isNeeded(component.getState())) { + if (getWidget().popup.captionWrapper != null) { + getWidget().popup.captionWrapper.updateCaption(); + } else { + getWidget().popup.captionWrapper = new VCaptionWrapper( + component, getConnection()); + getWidget().popup.setWidget(getWidget().popup.captionWrapper); + getWidget().popup.captionWrapper.updateCaption(); + } + } else { + if (getWidget().popup.captionWrapper != null) { + getWidget().popup + .setWidget(getWidget().popup.popupComponentWidget); + } + } + } + + @Override + public VPopupView getWidget() { + return (VPopupView) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPopupView.class); + } + + public void postLayout() { + if (centerAfterLayout) { + centerAfterLayout = false; + getWidget().center(); + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java b/src/com/vaadin/terminal/gwt/client/ui/popupview/VPopupView.java index 907e11ac2d..da48975726 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java +++ b/src/com/vaadin/terminal/gwt/client/ui/popupview/VPopupView.java @@ -1,11 +1,10 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.popupview; import java.util.HashSet; import java.util.Iterator; -import java.util.NoSuchElementException; import java.util.Set; import com.google.gwt.event.dom.client.ClickEvent; @@ -24,29 +23,25 @@ import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation.Size; -import com.vaadin.terminal.gwt.client.RenderSpace; +import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; import com.vaadin.terminal.gwt.client.VCaptionWrapper; import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.VOverlay; import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; -public class VPopupView extends HTML implements Container, Iterable<Widget> { +public class VPopupView extends HTML { public static final String CLASSNAME = "v-popupview"; /** For server-client communication */ - private String uidlId; - private ApplicationConnection client; + String uidlId; + ApplicationConnection client; /** This variable helps to communicate popup visibility to the server */ - private boolean hostPopupVisible; + boolean hostPopupVisible; - private final CustomPopup popup; + final CustomPopup popup; private final Label loading = new Label(); /** @@ -82,61 +77,6 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { } /** - * - * - * @see com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal.gwt.client.UIDL, - * com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // This call should be made first. Ensure correct implementation, - // and don't let the containing layout manage caption. - if (client.updateComponent(this, uidl, false)) { - return; - } - // These are for future server connections - this.client = client; - uidlId = uidl.getId(); - - hostPopupVisible = uidl.getBooleanVariable("popupVisibility"); - - setHTML(uidl.getStringAttribute("html")); - - if (uidl.hasAttribute("hideOnMouseOut")) { - popup.setHideOnMouseOut(uidl.getBooleanAttribute("hideOnMouseOut")); - } - - // Render the popup if visible and show it. - if (hostPopupVisible) { - UIDL popupUIDL = uidl.getChildUIDL(0); - - // showPopupOnTop(popup, hostReference); - preparePopup(popup); - popup.updateFromUIDL(popupUIDL, client); - if (uidl.hasAttribute("style")) { - final String[] styles = uidl.getStringAttribute("style").split( - " "); - final StringBuffer styleBuf = new StringBuffer(); - final String primaryName = popup.getStylePrimaryName(); - styleBuf.append(primaryName); - for (int i = 0; i < styles.length; i++) { - styleBuf.append(" "); - styleBuf.append(primaryName); - styleBuf.append("-"); - styleBuf.append(styles[i]); - } - popup.setStyleName(styleBuf.toString()); - } else { - popup.setStyleName(popup.getStylePrimaryName()); - } - showPopup(popup); - - // The popup shouldn't be visible, try to hide it. - } else { - popup.hide(); - } - }// updateFromUIDL - - /** * Update popup visibility to server * * @param visibility @@ -149,7 +89,7 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { } } - private void preparePopup(final CustomPopup popup) { + void preparePopup(final CustomPopup popup) { popup.setVisible(false); popup.show(); } @@ -166,6 +106,12 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { * @param popup */ protected void showPopup(final CustomPopup popup) { + popup.setPopupPosition(0, 0); + + popup.setVisible(true); + } + + void center() { int windowTop = RootPanel.get().getAbsoluteTop(); int windowLeft = RootPanel.get().getAbsoluteLeft(); int windowRight = windowLeft + RootPanel.get().getOffsetWidth(); @@ -200,8 +146,6 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { } popup.setPopupPosition(left, top); - - popup.setVisible(true); } /** @@ -230,9 +174,9 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { */ protected class CustomPopup extends VOverlay { - private Paintable popupComponentPaintable = null; - private Widget popupComponentWidget = null; - private VCaptionWrapper captionWrapper = null; + private ComponentConnector popupComponentPaintable = null; + Widget popupComponentWidget = null; + VCaptionWrapper captionWrapper = null; private boolean hasHadMouseOver = false; private boolean hideOnMouseOut = true; @@ -285,7 +229,6 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { public void hide(boolean autoClosed) { hiding = true; syncChildren(); - unregisterPaintables(); if (popupComponentWidget != null && popupComponentWidget != loading) { remove(popupComponentWidget); } @@ -346,26 +289,16 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - Paintable newPopupComponent = client.getPaintable(uidl + ComponentConnector newPopupComponent = client.getPaintable(uidl .getChildUIDL(0)); if (newPopupComponent != popupComponentPaintable) { - - setWidget((Widget) newPopupComponent); - - popupComponentWidget = (Widget) newPopupComponent; - + Widget newWidget = newPopupComponent.getWidget(); + setWidget(newWidget); + popupComponentWidget = newWidget; popupComponentPaintable = newPopupComponent; } - popupComponentPaintable - .updateFromUIDL(uidl.getChildUIDL(0), client); - } - - public void unregisterPaintables() { - if (popupComponentPaintable != null) { - client.unregisterPaintable(popupComponentPaintable); - } } public void setHideOnMouseOut(boolean hideOnMouseOut) { @@ -404,69 +337,6 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { }// class CustomPopup - // Container methods - - public RenderSpace getAllocatedSpace(Widget child) { - Size popupExtra = calculatePopupExtra(); - - return new RenderSpace(RootPanel.get().getOffsetWidth() - - popupExtra.getWidth(), RootPanel.get().getOffsetHeight() - - popupExtra.getHeight()); - } - - /** - * Calculate extra space taken by the popup decorations - * - * @return - */ - protected Size calculatePopupExtra() { - Element pe = popup.getElement(); - Element ipe = popup.getContainerElement(); - - // border + padding - int width = Util.getRequiredWidth(pe) - Util.getRequiredWidth(ipe); - int height = Util.getRequiredHeight(pe) - Util.getRequiredHeight(ipe); - - return new Size(width, height); - } - - public boolean hasChildComponent(Widget component) { - if (popup.popupComponentWidget != null) { - return popup.popupComponentWidget == component; - } else { - return false; - } - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - popup.setWidget(newComponent); - popup.popupComponentWidget = newComponent; - } - - public boolean requestLayout(Set<Paintable> child) { - popup.updateShadowSizeAndPosition(); - return true; - } - - public void updateCaption(Paintable component, UIDL uidl) { - if (VCaption.isNeeded(uidl)) { - if (popup.captionWrapper != null) { - popup.captionWrapper.updateCaption(uidl); - } else { - popup.captionWrapper = new VCaptionWrapper(component, client); - popup.setWidget(popup.captionWrapper); - popup.captionWrapper.updateCaption(uidl); - } - } else { - if (popup.captionWrapper != null) { - popup.setWidget(popup.popupComponentWidget); - } - } - - popup.popupComponentWidget = (Widget) component; - popup.popupComponentPaintable = component; - } - @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -475,30 +345,4 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { } } - public Iterator<Widget> iterator() { - return new Iterator<Widget>() { - - int pos = 0; - - public boolean hasNext() { - // There is a child widget only if next() has not been called. - return (pos == 0); - } - - public Widget next() { - // Next can be called only once to return the popup. - if (pos != 0) { - throw new NoSuchElementException(); - } - pos++; - return popup; - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - }; - } - }// class VPopupView diff --git a/src/com/vaadin/terminal/gwt/client/ui/progressindicator/ProgressIndicatorConnector.java b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/ProgressIndicatorConnector.java new file mode 100644 index 0000000000..09fa5fb44e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/ProgressIndicatorConnector.java @@ -0,0 +1,66 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.progressindicator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.ProgressIndicator; + +@Connect(ProgressIndicator.class) +public class ProgressIndicatorConnector extends AbstractFieldConnector + implements Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (!isRealUpdate(uidl)) { + return; + } + + // Save details + getWidget().client = client; + + getWidget().indeterminate = uidl.getBooleanAttribute("indeterminate"); + + if (getWidget().indeterminate) { + String basename = VProgressIndicator.CLASSNAME + "-indeterminate"; + getWidget().addStyleName(basename); + if (!isEnabled()) { + getWidget().addStyleName(basename + "-disabled"); + } else { + getWidget().removeStyleName(basename + "-disabled"); + } + } else { + try { + final float f = Float.parseFloat(uidl + .getStringAttribute("state")); + final int size = Math.round(100 * f); + DOM.setStyleAttribute(getWidget().indicator, "width", size + + "%"); + } catch (final Exception e) { + } + } + + if (isEnabled()) { + getWidget().interval = uidl.getIntAttribute("pollinginterval"); + getWidget().poller.scheduleRepeating(getWidget().interval); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VProgressIndicator.class); + } + + @Override + public VProgressIndicator getWidget() { + return (VProgressIndicator) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/VProgressIndicator.java index d5eac590ad..bc64efb60a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java +++ b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/VProgressIndicator.java @@ -2,27 +2,25 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.progressindicator; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; -public class VProgressIndicator extends Widget implements Paintable { +public class VProgressIndicator extends Widget { - private static final String CLASSNAME = "v-progressindicator"; + public static final String CLASSNAME = "v-progressindicator"; Element wrapper = DOM.createDiv(); Element indicator = DOM.createDiv(); - private ApplicationConnection client; - private final Poller poller; - private boolean indeterminate = false; + protected ApplicationConnection client; + protected final Poller poller; + protected boolean indeterminate = false; private boolean pollerSuspendedDueDetach; - private int interval; + protected int interval; public VProgressIndicator() { setElement(DOM.createDiv()); @@ -34,38 +32,6 @@ public class VProgressIndicator extends Widget implements Paintable { poller = new Poller(); } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - if (!uidl.getBooleanAttribute("cached")) { - poller.cancel(); - } - if (client.updateComponent(this, uidl, true)) { - return; - } - - indeterminate = uidl.getBooleanAttribute("indeterminate"); - - if (indeterminate) { - String basename = CLASSNAME + "-indeterminate"; - VProgressIndicator.setStyleName(getElement(), basename, true); - VProgressIndicator.setStyleName(getElement(), basename - + "-disabled", uidl.getBooleanAttribute("disabled")); - } else { - try { - final float f = Float.parseFloat(uidl - .getStringAttribute("state")); - final int size = Math.round(100 * f); - DOM.setStyleAttribute(indicator, "width", size + "%"); - } catch (final Exception e) { - } - } - - if (!uidl.getBooleanAttribute("disabled")) { - interval = uidl.getIntAttribute("pollinginterval"); - poller.scheduleRepeating(interval); - } - } - @Override protected void onAttach() { super.onAttach(); @@ -102,5 +68,4 @@ public class VProgressIndicator extends Widget implements Paintable { } } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/richtextarea/RichTextAreaConnector.java b/src/com/vaadin/terminal/gwt/client/ui/richtextarea/RichTextAreaConnector.java new file mode 100644 index 0000000000..01e79bc1c3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/richtextarea/RichTextAreaConnector.java @@ -0,0 +1,78 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.richtextarea; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.ui.RichTextArea; + +@Connect(value = RichTextArea.class, loadStyle = LoadStyle.LAZY) +public class RichTextAreaConnector extends AbstractFieldConnector implements + Paintable, BeforeShortcutActionListener { + + public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (uidl.hasVariable("text")) { + getWidget().currentValue = uidl.getStringVariable("text"); + if (getWidget().rta.isAttached()) { + getWidget().rta.setHTML(getWidget().currentValue); + } else { + getWidget().html.setHTML(getWidget().currentValue); + } + } + if (isRealUpdate(uidl)) { + getWidget().setEnabled(isEnabled()); + } + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().setReadOnly(isReadOnly()); + getWidget().immediate = getState().isImmediate(); + int newMaxLength = uidl.hasAttribute("maxLength") ? uidl + .getIntAttribute("maxLength") : -1; + if (newMaxLength >= 0) { + if (getWidget().maxLength == -1) { + getWidget().keyPressHandler = getWidget().rta + .addKeyPressHandler(getWidget()); + } + getWidget().maxLength = newMaxLength; + } else if (getWidget().maxLength != -1) { + getWidget().getElement().setAttribute("maxlength", ""); + getWidget().maxLength = -1; + getWidget().keyPressHandler.removeHandler(); + } + + if (uidl.hasAttribute("selectAll")) { + getWidget().selectAll(); + } + + } + + public void onBeforeShortcutAction(Event e) { + getWidget().synchronizeContentToServer(); + } + + @Override + public VRichTextArea getWidget() { + return (VRichTextArea) super.getWidget(); + }; + + @Override + protected Widget createWidget() { + return GWT.create(VRichTextArea.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/richtextarea/VRichTextArea.java b/src/com/vaadin/terminal/gwt/client/ui/richtextarea/VRichTextArea.java index bf0a423474..eb062a3799 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/richtextarea/VRichTextArea.java +++ b/src/com/vaadin/terminal/gwt/client/ui/richtextarea/VRichTextArea.java @@ -17,7 +17,6 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; @@ -27,12 +26,10 @@ import com.google.gwt.user.client.ui.RichTextArea; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.ui.Field; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; /** @@ -41,9 +38,8 @@ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHan * @author Vaadin Ltd. * */ -public class VRichTextArea extends Composite implements Paintable, Field, - ChangeHandler, BlurHandler, KeyPressHandler, KeyDownHandler, - BeforeShortcutActionListener, Focusable { +public class VRichTextArea extends Composite implements Field, ChangeHandler, + BlurHandler, KeyPressHandler, KeyDownHandler, Focusable { /** * The input node CSS classname. @@ -54,13 +50,13 @@ public class VRichTextArea extends Composite implements Paintable, Field, protected ApplicationConnection client; - private boolean immediate = false; + boolean immediate = false; - private RichTextArea rta; + RichTextArea rta; private VRichTextToolbar formatter; - private HTML html = new HTML(); + HTML html = new HTML(); private final FlowPanel fp = new FlowPanel(); @@ -69,15 +65,15 @@ public class VRichTextArea extends Composite implements Paintable, Field, private int extraHorizontalPixels = -1; private int extraVerticalPixels = -1; - private int maxLength = -1; + int maxLength = -1; private int toolbarNaturalWidth = 500; - private HandlerRegistration keyPressHandler; + HandlerRegistration keyPressHandler; private ShortcutActionHandlerOwner hasShortcutActionHandler; - private String currentValue = ""; + String currentValue = ""; private boolean readOnly = false; @@ -127,48 +123,7 @@ public class VRichTextArea extends Composite implements Paintable, Field, } } - public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { - this.client = client; - id = uidl.getId(); - - if (uidl.hasVariable("text")) { - currentValue = uidl.getStringVariable("text"); - if (rta.isAttached()) { - rta.setHTML(currentValue); - } else { - html.setHTML(currentValue); - } - } - if (!uidl.hasAttribute("cached")) { - setEnabled(!uidl.getBooleanAttribute("disabled")); - } - - if (client.updateComponent(this, uidl, true)) { - return; - } - - setReadOnly(uidl.getBooleanAttribute("readonly")); - immediate = uidl.getBooleanAttribute("immediate"); - int newMaxLength = uidl.hasAttribute("maxLength") ? uidl - .getIntAttribute("maxLength") : -1; - if (newMaxLength >= 0) { - if (maxLength == -1) { - keyPressHandler = rta.addKeyPressHandler(this); - } - maxLength = newMaxLength; - } else if (maxLength != -1) { - getElement().setAttribute("maxlength", ""); - maxLength = -1; - keyPressHandler.removeHandler(); - } - - if (uidl.hasAttribute("selectAll")) { - selectAll(); - } - - } - - private void selectAll() { + void selectAll() { /* * There is a timing issue if trying to select all immediately on first * render. Simple deferred command is not enough. Using Timer with @@ -190,7 +145,7 @@ public class VRichTextArea extends Composite implements Paintable, Field, }.schedule(320); } - private void setReadOnly(boolean b) { + void setReadOnly(boolean b) { if (isReadOnly() != b) { swapEditableArea(); readOnly = b; @@ -346,7 +301,8 @@ public class VRichTextArea extends Composite implements Paintable, Field, if (shortcutHandler != null) { shortcutHandler .handleKeyboardEvent(com.google.gwt.user.client.Event - .as(event.getNativeEvent()), this); + .as(event.getNativeEvent()), + ConnectorMap.get(client).getConnector(this)); } } @@ -364,10 +320,6 @@ public class VRichTextArea extends Composite implements Paintable, Field, return hasShortcutActionHandler; } - public void onBeforeShortcutAction(Event e) { - synchronizeContentToServer(); - } - public int getTabIndex() { return rta.getTabIndex(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java new file mode 100644 index 0000000000..46f82d60b7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java @@ -0,0 +1,423 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.root; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.History; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.web.bindery.event.shared.HandlerRegistration; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.layout.MayScrollChildren; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; +import com.vaadin.ui.Root; + +@Connect(value = Root.class, loadStyle = LoadStyle.EAGER) +public class RootConnector extends AbstractComponentContainerConnector + implements Paintable, MayScrollChildren { + + private RootServerRpc rpc = RpcProxy.create(RootServerRpc.class, this); + + private HandlerRegistration childStateChangeHandlerRegistration; + + private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() { + public void onStateChanged(StateChangeEvent stateChangeEvent) { + // TODO Should use a more specific handler that only reacts to + // size changes + onChildSizeChange(); + } + }; + + @Override + protected void init() { + super.init(); + } + + public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { + ConnectorMap paintableMap = ConnectorMap.get(getConnection()); + getWidget().rendering = true; + getWidget().id = getConnectorId(); + boolean firstPaint = getWidget().connection == null; + getWidget().connection = client; + + getWidget().immediate = getState().isImmediate(); + getWidget().resizeLazy = uidl.hasAttribute(VRoot.RESIZE_LAZY); + String newTheme = uidl.getStringAttribute("theme"); + if (getWidget().theme != null && !newTheme.equals(getWidget().theme)) { + // Complete page refresh is needed due css can affect layout + // calculations etc + getWidget().reloadHostPage(); + } else { + getWidget().theme = newTheme; + } + // this also implicitly removes old styles + String styles = ""; + styles += getWidget().getStylePrimaryName() + " "; + if (getState().hasStyles()) { + for (String style : getState().getStyles()) { + styles += style + " "; + } + } + if (!client.getConfiguration().isStandalone()) { + styles += getWidget().getStylePrimaryName() + "-embedded"; + } + getWidget().setStyleName(styles.trim()); + + clickEventHandler.handleEventHandlerRegistration(); + + if (!getWidget().isEmbedded() && getState().getCaption() != null) { + // only change window title if we're in charge of the whole page + com.google.gwt.user.client.Window.setTitle(getState().getCaption()); + } + + // Process children + int childIndex = 0; + + // Open URL:s + boolean isClosed = false; // was this window closed? + while (childIndex < uidl.getChildCount() + && "open".equals(uidl.getChildUIDL(childIndex).getTag())) { + final UIDL open = uidl.getChildUIDL(childIndex); + final String url = client.translateVaadinUri(open + .getStringAttribute("src")); + final String target = open.getStringAttribute("name"); + if (target == null) { + // source will be opened to this browser window, but we may have + // to finish rendering this window in case this is a download + // (and window stays open). + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + VRoot.goTo(url); + } + }); + } else if ("_self".equals(target)) { + // This window is closing (for sure). Only other opens are + // relevant in this change. See #3558, #2144 + isClosed = true; + VRoot.goTo(url); + } else { + String options; + if (open.hasAttribute("border")) { + if (open.getStringAttribute("border").equals("minimal")) { + options = "menubar=yes,location=no,status=no"; + } else { + options = "menubar=no,location=no,status=no"; + } + + } else { + options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes"; + } + + if (open.hasAttribute("width")) { + int w = open.getIntAttribute("width"); + options += ",width=" + w; + } + if (open.hasAttribute("height")) { + int h = open.getIntAttribute("height"); + options += ",height=" + h; + } + + Window.open(url, target, options); + } + childIndex++; + } + if (isClosed) { + // don't render the content, something else will be opened to this + // browser view + getWidget().rendering = false; + return; + } + + // Handle other UIDL children + UIDL childUidl; + while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) { + String tag = childUidl.getTag().intern(); + if (tag == "actions") { + if (getWidget().actionHandler == null) { + getWidget().actionHandler = new ShortcutActionHandler( + getWidget().id, client); + } + getWidget().actionHandler.updateActionMap(childUidl); + } else if (tag == "execJS") { + String script = childUidl.getStringAttribute("script"); + VRoot.eval(script); + } else if (tag == "notifications") { + for (final Iterator<?> it = childUidl.getChildIterator(); it + .hasNext();) { + final UIDL notification = (UIDL) it.next(); + VNotification.showNotification(client, notification); + } + } + } + + if (uidl.hasAttribute("focused")) { + // set focused component when render phase is finished + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + ComponentConnector paintable = (ComponentConnector) uidl + .getPaintableAttribute("focused", getConnection()); + + final Widget toBeFocused = paintable.getWidget(); + /* + * Two types of Widgets can be focused, either implementing + * GWT HasFocus of a thinner Vaadin specific Focusable + * interface. + */ + if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) { + final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused; + toBeFocusedWidget.setFocus(true); + } else if (toBeFocused instanceof Focusable) { + ((Focusable) toBeFocused).focus(); + } else { + VConsole.log("Could not focus component"); + } + } + }); + } + + // Add window listeners on first paint, to prevent premature + // variablechanges + if (firstPaint) { + Window.addWindowClosingHandler(getWidget()); + Window.addResizeHandler(getWidget()); + } + + // finally set scroll position from UIDL + if (uidl.hasVariable("scrollTop")) { + getWidget().scrollable = true; + getWidget().scrollTop = uidl.getIntVariable("scrollTop"); + DOM.setElementPropertyInt(getWidget().getElement(), "scrollTop", + getWidget().scrollTop); + getWidget().scrollLeft = uidl.getIntVariable("scrollLeft"); + DOM.setElementPropertyInt(getWidget().getElement(), "scrollLeft", + getWidget().scrollLeft); + } else { + getWidget().scrollable = false; + } + + if (uidl.hasAttribute("scrollTo")) { + final ComponentConnector connector = (ComponentConnector) uidl + .getPaintableAttribute("scrollTo", getConnection()); + scrollIntoView(connector); + } + + if (uidl.hasAttribute(VRoot.FRAGMENT_VARIABLE)) { + getWidget().currentFragment = uidl + .getStringAttribute(VRoot.FRAGMENT_VARIABLE); + if (!getWidget().currentFragment.equals(History.getToken())) { + History.newItem(getWidget().currentFragment, true); + } + } else { + // Initial request for which the server doesn't yet have a fragment + // (and haven't shown any interest in getting one) + getWidget().currentFragment = History.getToken(); + + // Include current fragment in the next request + client.updateVariable(getWidget().id, VRoot.FRAGMENT_VARIABLE, + getWidget().currentFragment, false); + } + + getWidget().rendering = false; + } + + public void init(String rootPanelId, + ApplicationConnection applicationConnection) { + DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN + | Event.ONSCROLL); + + // iview is focused when created so element needs tabIndex + // 1 due 0 is at the end of natural tabbing order + DOM.setElementProperty(getWidget().getElement(), "tabIndex", "1"); + + RootPanel root = RootPanel.get(rootPanelId); + + // Remove the v-app-loading or any splash screen added inside the div by + // the user + root.getElement().setInnerHTML(""); + + root.addStyleName("v-theme-" + + applicationConnection.getConfiguration().getThemeName()); + + root.add(getWidget()); + + if (applicationConnection.getConfiguration().isStandalone()) { + // set focus to iview element by default to listen possible keyboard + // shortcuts. For embedded applications this is unacceptable as we + // don't want to steal focus from the main page nor we don't want + // side-effects from focusing (scrollIntoView). + getWidget().getElement().focus(); + } + } + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.click(mouseDetails); + } + + }; + + public void updateCaption(ComponentConnector component) { + // NOP The main view never draws caption for its layout + } + + @Override + public VRoot getWidget() { + return (VRoot) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VRoot.class); + } + + protected ComponentConnector getContent() { + return (ComponentConnector) getState().getContent(); + } + + protected void onChildSizeChange() { + ComponentConnector child = getContent(); + Style childStyle = child.getWidget().getElement().getStyle(); + /* + * Must set absolute position if the child has relative height and + * there's a chance of horizontal scrolling as some browsers will + * otherwise not take the scrollbar into account when calculating the + * height. Assuming v-view does not have an undefined width for now, see + * #8460. + */ + if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) { + childStyle.setPosition(Position.ABSOLUTE); + } else { + childStyle.clearPosition(); + } + } + + /** + * Checks if the given sub window is a child of this Root Connector + * + * @deprecated Should be replaced by a more generic mechanism for getting + * non-ComponentConnector children + * @param wc + * @return + */ + @Deprecated + public boolean hasSubWindow(WindowConnector wc) { + return getChildren().contains(wc); + } + + /** + * Return an iterator for current subwindows. This method is meant for + * testing purposes only. + * + * @return + */ + public List<WindowConnector> getSubWindows() { + ArrayList<WindowConnector> windows = new ArrayList<WindowConnector>(); + for (ComponentConnector child : getChildren()) { + if (child instanceof WindowConnector) { + windows.add((WindowConnector) child); + } + } + return windows; + } + + @Override + public RootState getState() { + return (RootState) super.getState(); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + ComponentConnector oldChild = null; + ComponentConnector newChild = getContent(); + + for (ComponentConnector c : event.getOldChildren()) { + if (!(c instanceof WindowConnector)) { + oldChild = c; + break; + } + } + + if (oldChild != newChild) { + if (childStateChangeHandlerRegistration != null) { + childStateChangeHandlerRegistration.removeHandler(); + childStateChangeHandlerRegistration = null; + } + getWidget().setWidget(newChild.getWidget()); + childStateChangeHandlerRegistration = newChild + .addStateChangeHandler(childStateChangeHandler); + // Must handle new child here as state change events are already + // fired + onChildSizeChange(); + } + + for (ComponentConnector c : getChildren()) { + if (c instanceof WindowConnector) { + WindowConnector wc = (WindowConnector) c; + wc.setWindowOrderAndPosition(); + } + } + + // Close removed sub windows + for (ComponentConnector c : event.getOldChildren()) { + if (c.getParent() != this && c instanceof WindowConnector) { + ((WindowConnector) c).getWidget().hide(); + } + } + } + + /** + * Tries to scroll the viewport so that the given connector is in view. + * + * @param componentConnector + * The connector which should be visible + * + */ + public void scrollIntoView(final ComponentConnector componentConnector) { + if (componentConnector == null) { + return; + } + + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + componentConnector.getWidget().getElement().scrollIntoView(); + } + }); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRpc.java new file mode 100644 index 0000000000..389500949d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.root; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; + +public interface RootServerRpc extends ClickRpc, ServerRpc { + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootState.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootState.java new file mode 100644 index 0000000000..85d5e45022 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootState.java @@ -0,0 +1,20 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.root; + +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.Connector; + +public class RootState extends ComponentState { + private Connector content; + + public Connector getContent() { + return content; + } + + public void setContent(Connector content) { + this.content = content; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java new file mode 100644 index 0000000000..13fc55d7ea --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java @@ -0,0 +1,321 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.root; + +import java.util.ArrayList; + +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.event.logical.shared.ResizeEvent; +import com.google.gwt.event.logical.shared.ResizeHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.History; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.SimplePanel; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; + +/** + * + */ +public class VRoot extends SimplePanel implements ResizeHandler, + Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable { + + public static final String FRAGMENT_VARIABLE = "fragment"; + + private static final String CLASSNAME = "v-view"; + + public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain"; + + String theme; + + String id; + + ShortcutActionHandler actionHandler; + + /** stored width for IE resize optimization */ + private int width; + + /** stored height for IE resize optimization */ + private int height; + + ApplicationConnection connection; + + /** Identifies the click event */ + public static final String CLICK_EVENT_ID = "click"; + + /** + * We are postponing resize process with IE. IE bugs with scrollbars in some + * situations, that causes false onWindowResized calls. With Timer we will + * give IE some time to decide if it really wants to keep current size + * (scrollbars). + */ + private Timer resizeTimer; + + int scrollTop; + + int scrollLeft; + + boolean rendering; + + boolean scrollable; + + boolean immediate; + + boolean resizeLazy = false; + + /** + * Attribute name for the lazy resize setting . + */ + public static final String RESIZE_LAZY = "rL"; + + private HandlerRegistration historyHandlerRegistration; + + /** + * The current URI fragment, used to avoid sending updates if nothing has + * changed. + */ + String currentFragment; + + /** + * Listener for URI fragment changes. Notifies the server of the new value + * whenever the value changes. + */ + private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() { + public void onValueChange(ValueChangeEvent<String> event) { + String newFragment = event.getValue(); + + // Send the new fragment to the server if it has changed + if (!newFragment.equals(currentFragment) && connection != null) { + currentFragment = newFragment; + connection.updateVariable(id, FRAGMENT_VARIABLE, newFragment, + true); + } + } + }; + + private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200, + new ScheduledCommand() { + public void execute() { + windowSizeMaybeChanged(Window.getClientWidth(), + Window.getClientHeight()); + } + + }); + + public VRoot() { + super(); + setStyleName(CLASSNAME); + + // Allow focusing the view by using the focus() method, the view + // should not be in the document focus flow + getElement().setTabIndex(-1); + } + + @Override + protected void onAttach() { + super.onAttach(); + historyHandlerRegistration = History + .addValueChangeHandler(historyChangeHandler); + currentFragment = History.getToken(); + } + + @Override + protected void onDetach() { + super.onDetach(); + historyHandlerRegistration.removeHandler(); + historyHandlerRegistration = null; + } + + /** + * Called when the window might have been resized. + * + * @param newWidth + * The new width of the window + * @param newHeight + * The new height of the window + */ + protected void windowSizeMaybeChanged(int newWidth, int newHeight) { + boolean changed = false; + ComponentConnector connector = ConnectorMap.get(connection) + .getConnector(this); + if (width != newWidth) { + width = newWidth; + changed = true; + connector.getLayoutManager().reportOuterWidth(connector, newWidth); + VConsole.log("New window width: " + width); + } + if (height != newHeight) { + height = newHeight; + changed = true; + connector.getLayoutManager() + .reportOuterHeight(connector, newHeight); + VConsole.log("New window height: " + height); + } + if (changed) { + VConsole.log("Running layout functions due to window resize"); + + sendClientResized(); + + connector.getLayoutManager().layoutNow(); + } + } + + public String getTheme() { + return theme; + } + + /** + * Used to reload host page on theme changes. + */ + static native void reloadHostPage() + /*-{ + $wnd.location.reload(); + }-*/; + + /** + * Evaluate the given script in the browser document. + * + * @param script + * Script to be executed. + */ + static native void eval(String script) + /*-{ + try { + if (script == null) return; + $wnd.eval(script); + } catch (e) { + } + }-*/; + + /** + * Returns true if the body is NOT generated, i.e if someone else has made + * the page that we're running in. Otherwise we're in charge of the whole + * page. + * + * @return true if we're running embedded + */ + public boolean isEmbedded() { + return !getElement().getOwnerDocument().getBody().getClassName() + .contains(ApplicationConnection.GENERATED_BODY_CLASSNAME); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + int type = DOM.eventGetType(event); + if (type == Event.ONKEYDOWN && actionHandler != null) { + actionHandler.handleKeyboardEvent(event); + return; + } else if (scrollable && type == Event.ONSCROLL) { + updateScrollPosition(); + } + } + + /** + * Updates scroll position from DOM and saves variables to server. + */ + private void updateScrollPosition() { + int oldTop = scrollTop; + int oldLeft = scrollLeft; + scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop"); + scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft"); + if (connection != null && !rendering) { + if (oldTop != scrollTop) { + connection.updateVariable(id, "scrollTop", scrollTop, false); + } + if (oldLeft != scrollLeft) { + connection.updateVariable(id, "scrollLeft", scrollLeft, false); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google + * .gwt.event.logical.shared.ResizeEvent) + */ + public void onResize(ResizeEvent event) { + onResize(); + } + + /** + * Called when a resize event is received. + */ + void onResize() { + /* + * IE (pre IE9 at least) will give us some false resize events due to + * problems with scrollbars. Firefox 3 might also produce some extra + * events. We postpone both the re-layouting and the server side event + * for a while to deal with these issues. + * + * We may also postpone these events to avoid slowness when resizing the + * browser window. Constantly recalculating the layout causes the resize + * operation to be really slow with complex layouts. + */ + boolean lazy = resizeLazy || BrowserInfo.get().isIE8(); + + if (lazy) { + delayedResizeExecutor.trigger(); + } else { + windowSizeMaybeChanged(Window.getClientWidth(), + Window.getClientHeight()); + } + } + + /** + * Send new dimensions to the server. + */ + private void sendClientResized() { + connection.updateVariable(id, "height", height, false); + connection.updateVariable(id, "width", width, immediate); + } + + public native static void goTo(String url) + /*-{ + $wnd.location = url; + }-*/; + + public void onWindowClosing(Window.ClosingEvent event) { + // Change focus on this window in order to ensure that all state is + // collected from textfields + // TODO this is a naive hack, that only works with text fields and may + // cause some odd issues. Should be replaced with a decent solution, see + // also related BeforeShortcutActionListener interface. Same interface + // might be usable here. + VTextField.flushChangesFromFocusedTextField(); + } + + private native static void loadAppIdListFromDOM(ArrayList<String> list) + /*-{ + var j; + for(j in $wnd.vaadin.vaadinConfigurations) { + list.@java.util.Collection::add(Ljava/lang/Object;)(j); + } + }-*/; + + public ShortcutActionHandler getShortcutActionHandler() { + return actionHandler; + } + + public void focus() { + getElement().focus(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/slider/SliderConnector.java b/src/com/vaadin/terminal/gwt/client/ui/slider/SliderConnector.java new file mode 100644 index 0000000000..9cd3c35fee --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/slider/SliderConnector.java @@ -0,0 +1,77 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.slider; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.Slider; + +@Connect(Slider.class) +public class SliderConnector extends AbstractFieldConnector implements + Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().immediate = getState().isImmediate(); + getWidget().disabled = !isEnabled(); + getWidget().readonly = isReadOnly(); + + getWidget().vertical = uidl.hasAttribute("vertical"); + + // TODO should style names be used? + + if (getWidget().vertical) { + getWidget().addStyleName(VSlider.CLASSNAME + "-vertical"); + } else { + getWidget().removeStyleName(VSlider.CLASSNAME + "-vertical"); + } + + getWidget().min = uidl.getDoubleAttribute("min"); + getWidget().max = uidl.getDoubleAttribute("max"); + getWidget().resolution = uidl.getIntAttribute("resolution"); + getWidget().value = new Double(uidl.getDoubleVariable("value")); + + getWidget().setFeedbackValue(getWidget().value); + + getWidget().buildBase(); + + if (!getWidget().vertical) { + // Draw handle with a delay to allow base to gain maximum width + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + getWidget().buildHandle(); + getWidget().setValue(getWidget().value, false); + } + }); + } else { + getWidget().buildHandle(); + getWidget().setValue(getWidget().value, false); + } + } + + @Override + public VSlider getWidget() { + return (VSlider) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VSlider.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java index 4a46346613..9ff614252d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java +++ b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ // -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.slider; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; @@ -16,12 +16,14 @@ import com.google.gwt.user.client.ui.HTML; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.ContainerResizedListener; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.SimpleFocusablePanel; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.VOverlay; -public class VSlider extends SimpleFocusablePanel implements Paintable, Field, +public class VSlider extends SimpleFocusablePanel implements Field, ContainerResizedListener { public static final String CLASSNAME = "v-slider"; @@ -36,19 +38,16 @@ public class VSlider extends SimpleFocusablePanel implements Paintable, Field, String id; - private boolean immediate; - private boolean disabled; - private boolean readonly; - private boolean scrollbarStyle; + boolean immediate; + boolean disabled; + boolean readonly; private int acceleration = 1; - private int handleSize; - private double min; - private double max; - private int resolution; - private Double value; - private boolean vertical; - private boolean arrows; + double min; + double max; + int resolution; + Double value; + boolean vertical; private final HTML feedback = new HTML("", false); private final VOverlay feedbackPopup = new VOverlay(true, false, true) { @@ -115,67 +114,7 @@ public class VSlider extends SimpleFocusablePanel implements Paintable, Field, feedbackPopup.setWidget(feedback); } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - this.client = client; - id = uidl.getId(); - - // Ensure correct implementation - if (client.updateComponent(this, uidl, true)) { - return; - } - - immediate = uidl.getBooleanAttribute("immediate"); - disabled = uidl.getBooleanAttribute("disabled"); - readonly = uidl.getBooleanAttribute("readonly"); - - vertical = uidl.hasAttribute("vertical"); - arrows = uidl.hasAttribute("arrows"); - - String style = ""; - if (uidl.hasAttribute("style")) { - style = uidl.getStringAttribute("style"); - } - - scrollbarStyle = style.indexOf("scrollbar") > -1; - - if (arrows) { - DOM.setStyleAttribute(smaller, "display", "block"); - DOM.setStyleAttribute(bigger, "display", "block"); - } - - if (vertical) { - addStyleName(CLASSNAME + "-vertical"); - } else { - removeStyleName(CLASSNAME + "-vertical"); - } - - min = uidl.getDoubleAttribute("min"); - max = uidl.getDoubleAttribute("max"); - resolution = uidl.getIntAttribute("resolution"); - value = new Double(uidl.getDoubleVariable("value")); - - setFeedbackValue(value); - - handleSize = uidl.getIntAttribute("hsize"); - - buildBase(); - - if (!vertical) { - // Draw handle with a delay to allow base to gain maximum width - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - buildHandle(); - setValue(value, false); - } - }); - } else { - buildHandle(); - setValue(value, false); - } - } - - private void setFeedbackValue(double value) { + void setFeedbackValue(double value) { String currentValue = "" + value; if (resolution == 0) { currentValue = "" + new Double(value).intValue(); @@ -198,7 +137,7 @@ public class VSlider extends SimpleFocusablePanel implements Paintable, Field, } } - private void buildBase() { + void buildBase() { final String styleAttribute = vertical ? "height" : "width"; final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; @@ -232,37 +171,17 @@ public class VSlider extends SimpleFocusablePanel implements Paintable, Field, // TODO attach listeners for focusing and arrow keys } - private void buildHandle() { - final String styleAttribute = vertical ? "height" : "width"; + void buildHandle() { final String handleAttribute = vertical ? "marginTop" : "marginLeft"; - final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; DOM.setStyleAttribute(handle, handleAttribute, "0"); - if (scrollbarStyle) { - // Only stretch the handle if scrollbar style is set. - int s = (int) (Double.parseDouble(DOM.getElementProperty(base, - domProperty)) / 100 * handleSize); - if (handleSize == -1) { - final int baseS = Integer.parseInt(DOM.getElementProperty(base, - domProperty)); - final double range = (max - min) * (resolution + 1) * 3; - s = (int) (baseS - range); - } - if (s < 3) { - s = 3; - } - DOM.setStyleAttribute(handle, styleAttribute, s + "px"); - } else { - DOM.setStyleAttribute(handle, styleAttribute, ""); - } - // Restore visibility DOM.setStyleAttribute(handle, "visibility", "visible"); } - private void setValue(Double value, boolean updateToServer) { + void setValue(Double value, boolean updateToServer) { if (value == null) { return; } @@ -300,9 +219,7 @@ public class VSlider extends SimpleFocusablePanel implements Paintable, Field, p = 0; } if (vertical) { - // IE6 rounding behaves a little unstable, reduce one pixel so the - // containing element (base) won't expand without limits - p = range - p - (BrowserInfo.get().isIE6() ? 1 : 0); + p = range - p; } final double pos = p; @@ -356,7 +273,7 @@ public class VSlider extends SimpleFocusablePanel implements Paintable, Field, } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { feedbackPopup.show(); } - if(Util.isTouchEvent(event)) { + if (Util.isTouchEvent(event)) { event.preventDefault(); // avoid simulated events event.stopPropagation(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java new file mode 100644 index 0000000000..b3921204dc --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java @@ -0,0 +1,206 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import java.util.LinkedList; +import java.util.List; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.dom.client.DomEvent.Type; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelState.SplitterState; +import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler; +import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; + +public abstract class AbstractSplitPanelConnector extends + AbstractComponentContainerConnector implements SimpleManagedLayout { + + private AbstractSplitPanelRpc rpc; + + @Override + protected void init() { + super.init(); + rpc = RpcProxy.create(AbstractSplitPanelRpc.class, this); + // TODO Remove + getWidget().client = getConnection(); + + getWidget().addHandler(new SplitterMoveHandler() { + + public void splitterMoved(SplitterMoveEvent event) { + String position = getWidget().getSplitterPosition(); + float pos = 0; + if (position.indexOf("%") > 0) { + // Send % values as a fraction to avoid that the splitter + // "jumps" when server responds with the integer pct value + // (e.g. dragged 16.6% -> should not jump to 17%) + pos = Float.valueOf(position.substring(0, + position.length() - 1)); + } else { + pos = Integer.parseInt(position.substring(0, + position.length() - 2)); + } + + rpc.setSplitterPosition(pos); + } + + }, SplitterMoveEvent.TYPE); + } + + public void updateCaption(ComponentConnector component) { + // TODO Implement caption handling + } + + ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + if ((Event.getEventsSunk(getWidget().splitter) & Event + .getTypeInt(type.getName())) != 0) { + // If we are already sinking the event for the splitter we do + // not want to additionally sink it for the root element + return getWidget().addHandler(handler, type); + } else { + return getWidget().addDomHandler(handler, type); + } + } + + @Override + protected boolean shouldFireEvent(DomEvent<?> event) { + Element target = event.getNativeEvent().getEventTarget().cast(); + if (!getWidget().splitter.isOrHasChild(target)) { + return false; + } + + return super.shouldFireEvent(event); + }; + + @Override + protected Element getRelativeToElement() { + return getWidget().splitter; + }; + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.splitterClick(mouseDetails); + } + + }; + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().immediate = getState().isImmediate(); + + getWidget().setEnabled(isEnabled()); + + clickEventHandler.handleEventHandlerRegistration(); + + if (getState().hasStyles()) { + getWidget().componentStyleNames = getState().getStyles(); + } else { + getWidget().componentStyleNames = new LinkedList<String>(); + } + + // Splitter updates + SplitterState splitterState = getState().getSplitterState(); + + getWidget().setLocked(splitterState.isLocked()); + getWidget().setPositionReversed(splitterState.isPositionReversed()); + + getWidget().setStylenames(); + + getWidget().position = splitterState.getPosition() + + splitterState.getPositionUnit(); + + // This is needed at least for cases like #3458 to take + // appearing/disappearing scrollbars into account. + getConnection().runDescendentsLayout(getWidget()); + + getLayoutManager().setNeedsLayout(this); + + } + + public void layout() { + VAbstractSplitPanel splitPanel = getWidget(); + splitPanel.setSplitPosition(splitPanel.position); + splitPanel.updateSizes(); + // Report relative sizes in other direction for quicker propagation + List<ComponentConnector> children = getChildren(); + for (ComponentConnector child : children) { + reportOtherDimension(child); + } + } + + private void reportOtherDimension(ComponentConnector child) { + LayoutManager layoutManager = getLayoutManager(); + if (this instanceof HorizontalSplitPanelConnector) { + if (child.isRelativeHeight()) { + int height = layoutManager.getInnerHeight(getWidget() + .getElement()); + layoutManager.reportHeightAssignedToRelative(child, height); + } + } else { + if (child.isRelativeWidth()) { + int width = layoutManager.getInnerWidth(getWidget() + .getElement()); + layoutManager.reportWidthAssignedToRelative(child, width); + } + } + } + + @Override + public VAbstractSplitPanel getWidget() { + return (VAbstractSplitPanel) super.getWidget(); + } + + @Override + protected abstract VAbstractSplitPanel createWidget(); + + @Override + public AbstractSplitPanelState getState() { + return (AbstractSplitPanelState) super.getState(); + } + + private ComponentConnector getFirstChild() { + return (ComponentConnector) getState().getFirstChild(); + } + + private ComponentConnector getSecondChild() { + return (ComponentConnector) getState().getSecondChild(); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + Widget newFirstChildWidget = null; + if (getFirstChild() != null) { + newFirstChildWidget = getFirstChild().getWidget(); + } + getWidget().setFirstWidget(newFirstChildWidget); + + Widget newSecondChildWidget = null; + if (getSecondChild() != null) { + newSecondChildWidget = getSecondChild().getWidget(); + } + getWidget().setSecondWidget(newSecondChildWidget); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelRpc.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelRpc.java new file mode 100644 index 0000000000..cc043838ff --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelRpc.java @@ -0,0 +1,28 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public interface AbstractSplitPanelRpc extends ServerRpc { + + /** + * Called when the position has been updated by the user. + * + * @param position + * The new position in % if the current unit is %, in px + * otherwise + */ + public void setSplitterPosition(float position); + + /** + * Called when a click event has occurred on the splitter. + * + * @param mouseDetails + * Details about the mouse when the event took place + */ + public void splitterClick(MouseEventDetails mouseDetails); + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java new file mode 100644 index 0000000000..8b80eed840 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java @@ -0,0 +1,88 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import java.io.Serializable; + +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.Connector; + +public class AbstractSplitPanelState extends ComponentState { + + private Connector firstChild = null; + private Connector secondChild = null; + private SplitterState splitterState = new SplitterState(); + + public boolean hasFirstChild() { + return firstChild != null; + } + + public boolean hasSecondChild() { + return secondChild != null; + } + + public Connector getFirstChild() { + return firstChild; + } + + public void setFirstChild(Connector firstChild) { + this.firstChild = firstChild; + } + + public Connector getSecondChild() { + return secondChild; + } + + public void setSecondChild(Connector secondChild) { + this.secondChild = secondChild; + } + + public SplitterState getSplitterState() { + return splitterState; + } + + public void setSplitterState(SplitterState splitterState) { + this.splitterState = splitterState; + } + + public static class SplitterState implements Serializable { + private float position; + private String positionUnit; + private boolean positionReversed = false; + private boolean locked = false; + + public float getPosition() { + return position; + } + + public void setPosition(float position) { + this.position = position; + } + + public String getPositionUnit() { + return positionUnit; + } + + public void setPositionUnit(String positionUnit) { + this.positionUnit = positionUnit; + } + + public boolean isPositionReversed() { + return positionReversed; + } + + public void setPositionReversed(boolean positionReversed) { + this.positionReversed = positionReversed; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/HorizontalSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/HorizontalSplitPanelConnector.java new file mode 100644 index 0000000000..7814ab8e13 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/HorizontalSplitPanelConnector.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.HorizontalSplitPanel; + +@Connect(value = HorizontalSplitPanel.class, loadStyle = LoadStyle.EAGER) +public class HorizontalSplitPanelConnector extends AbstractSplitPanelConnector { + + @Override + protected VAbstractSplitPanel createWidget() { + return GWT.create(VSplitPanelHorizontal.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanel.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java index 51e378cc0c..166e79e92e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java @@ -2,14 +2,12 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.splitpanel; -import java.util.Set; +import java.util.List; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; -import com.google.gwt.event.dom.client.DomEvent.Type; +import com.google.gwt.dom.client.Style; import com.google.gwt.event.dom.client.TouchCancelEvent; import com.google.gwt.event.dom.client.TouchCancelHandler; import com.google.gwt.event.dom.client.TouchEndEvent; @@ -19,8 +17,7 @@ import com.google.gwt.event.dom.client.TouchMoveHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; +import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; @@ -28,64 +25,21 @@ import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; -import com.vaadin.terminal.gwt.client.ContainerResizedListener; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.LayoutManager; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; -public class VSplitPanel extends ComplexPanel implements Container, - ContainerResizedListener { +public class VAbstractSplitPanel extends ComplexPanel { private boolean enabled = false; public static final String CLASSNAME = "v-splitpanel"; - public static final String SPLITTER_CLICK_EVENT_IDENTIFIER = "sp_click"; - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this, - SPLITTER_CLICK_EVENT_IDENTIFIER) { - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - if ((Event.getEventsSunk(splitter) & Event.getTypeInt(type - .getName())) != 0) { - // If we are already sinking the event for the splitter we do - // not want to additionally sink it for the root element - return addHandler(handler, type); - } else { - return addDomHandler(handler, type); - } - } - - @Override - public void onContextMenu( - com.google.gwt.event.dom.client.ContextMenuEvent event) { - Element target = event.getNativeEvent().getEventTarget().cast(); - if (splitter.isOrHasChild(target)) { - super.onContextMenu(event); - } - }; - - @Override - protected void fireClick(NativeEvent event) { - Element target = event.getEventTarget().cast(); - if (splitter.isOrHasChild(target)) { - super.fireClick(event); - } - } - - @Override - protected Element getRelativeToElement() { - return null; - } - - }; - public static final int ORIENTATION_HORIZONTAL = 0; public static final int ORIENTATION_VERTICAL = 1; @@ -94,9 +48,9 @@ public class VSplitPanel extends ComplexPanel implements Container, private int orientation = ORIENTATION_HORIZONTAL; - private Widget firstChild; + Widget firstChild; - private Widget secondChild; + Widget secondChild; private final Element wrapper = DOM.createDiv(); @@ -104,7 +58,7 @@ public class VSplitPanel extends ComplexPanel implements Container, private final Element secondContainer = DOM.createDiv(); - private final Element splitter = DOM.createDiv(); + final Element splitter = DOM.createDiv(); private boolean resizing; @@ -122,29 +76,16 @@ public class VSplitPanel extends ComplexPanel implements Container, private boolean positionReversed = false; - private String[] componentStyleNames; + List<String> componentStyleNames; private Element draggingCurtain; - private ApplicationConnection client; - - private String width = ""; - - private String height = ""; - - private RenderSpace firstRenderSpace = new RenderSpace(0, 0, true); - private RenderSpace secondRenderSpace = new RenderSpace(0, 0, true); + ApplicationConnection client; - RenderInformation renderInformation = new RenderInformation(); - - private String id; - - private boolean immediate; - - private boolean rendering = false; + boolean immediate; /* The current position of the split handle in either percentages or pixels */ - private String position; + String position; protected Element scrolledContainer; @@ -152,11 +93,11 @@ public class VSplitPanel extends ComplexPanel implements Container, private TouchScrollDelegate touchScrollDelegate; - public VSplitPanel() { + public VAbstractSplitPanel() { this(ORIENTATION_HORIZONTAL); } - public VSplitPanel(int orientation) { + public VAbstractSplitPanel(int orientation) { setElement(DOM.createDiv()); switch (orientation) { case ORIENTATION_HORIZONTAL: @@ -256,73 +197,6 @@ public class VSplitPanel extends ComplexPanel implements Container, + "-second-container"); } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - id = uidl.getId(); - rendering = true; - - immediate = uidl.hasAttribute("immediate"); - - if (client.updateComponent(this, uidl, true)) { - rendering = false; - return; - } - setEnabled(!uidl.getBooleanAttribute("disabled")); - - clickEventHandler.handleEventHandlerRegistration(client); - if (uidl.hasAttribute("style")) { - componentStyleNames = uidl.getStringAttribute("style").split(" "); - } else { - componentStyleNames = new String[0]; - } - - setLocked(uidl.getBooleanAttribute("locked")); - - setPositionReversed(uidl.getBooleanAttribute("reversed")); - - setStylenames(); - - position = uidl.getStringAttribute("position"); - setSplitPosition(position); - - final Paintable newFirstChild = client.getPaintable(uidl - .getChildUIDL(0)); - final Paintable newSecondChild = client.getPaintable(uidl - .getChildUIDL(1)); - if (firstChild != newFirstChild) { - if (firstChild != null) { - client.unregisterPaintable((Paintable) firstChild); - } - setFirstWidget((Widget) newFirstChild); - } - if (secondChild != newSecondChild) { - if (secondChild != null) { - client.unregisterPaintable((Paintable) secondChild); - } - setSecondWidget((Widget) newSecondChild); - } - newFirstChild.updateFromUIDL(uidl.getChildUIDL(0), client); - newSecondChild.updateFromUIDL(uidl.getChildUIDL(1), client); - - renderInformation.updateSize(getElement()); - - if (BrowserInfo.get().isIE7()) { - // Part III of IE7 hack - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - iLayout(); - } - }); - } - - // This is needed at least for cases like #3458 to take - // appearing/disappearing scrollbars into account. - client.runDescendentsLayout(this); - - rendering = false; - - } - @Override public boolean remove(Widget w) { boolean removed = super.remove(w); @@ -336,7 +210,7 @@ public class VSplitPanel extends ComplexPanel implements Container, return removed; } - private void setLocked(boolean newValue) { + void setLocked(boolean newValue) { if (locked != newValue) { locked = newValue; splitterSize = -1; @@ -344,7 +218,7 @@ public class VSplitPanel extends ComplexPanel implements Container, } } - private void setPositionReversed(boolean reversed) { + void setPositionReversed(boolean reversed) { if (positionReversed != reversed) { if (orientation == ORIENTATION_HORIZONTAL) { DOM.setStyleAttribute(splitter, "right", ""); @@ -358,49 +232,47 @@ public class VSplitPanel extends ComplexPanel implements Container, } } - private void setSplitPosition(String pos) { + void setSplitPosition(String pos) { if (pos == null) { return; } // Convert percentage values to pixels if (pos.indexOf("%") > 0) { - pos = Float.parseFloat(pos.substring(0, pos.length() - 1)) - / 100 - * (orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() - : getOffsetHeight()) + "px"; + int size = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() + : getOffsetHeight(); + float percentage = Float.parseFloat(pos.substring(0, + pos.length() - 1)); + pos = percentage / 100 * size + "px"; } + String attributeName; if (orientation == ORIENTATION_HORIZONTAL) { if (positionReversed) { - DOM.setStyleAttribute(splitter, "right", pos); + attributeName = "right"; } else { - DOM.setStyleAttribute(splitter, "left", pos); + attributeName = "left"; } } else { if (positionReversed) { - DOM.setStyleAttribute(splitter, "bottom", pos); + attributeName = "bottom"; } else { - DOM.setStyleAttribute(splitter, "top", pos); + attributeName = "top"; } } - iLayout(); - client.runDescendentsLayout(this); + Style style = splitter.getStyle(); + if (!pos.equals(style.getProperty(attributeName))) { + style.setProperty(attributeName, pos); + updateSizes(); + } } - /* - * Calculates absolutely positioned container places/sizes (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.NeedsLayout#layout() - */ - public void iLayout() { + void updateSizes() { if (!isAttached()) { return; } - renderInformation.updateSize(getElement()); - int wholeSize; int pixelPosition; @@ -430,12 +302,28 @@ public class VSplitPanel extends ComplexPanel implements Container, DOM.setStyleAttribute(secondContainer, "left", (pixelPosition + getSplitterSize()) + "px"); - int contentHeight = renderInformation.getRenderedSize().getHeight(); - firstRenderSpace.setHeight(contentHeight); - firstRenderSpace.setWidth(pixelPosition); - secondRenderSpace.setHeight(contentHeight); - secondRenderSpace.setWidth(secondContainerWidth); - + LayoutManager layoutManager = LayoutManager.get(client); + ConnectorMap connectorMap = ConnectorMap.get(client); + if (firstChild != null) { + ComponentConnector connector = connectorMap + .getConnector(firstChild); + if (connector.isRelativeWidth()) { + layoutManager.reportWidthAssignedToRelative(connector, + pixelPosition); + } else { + layoutManager.setNeedsMeasure(connector); + } + } + if (secondChild != null) { + ComponentConnector connector = connectorMap + .getConnector(secondChild); + if (connector.isRelativeWidth()) { + layoutManager.reportWidthAssignedToRelative(connector, + secondContainerWidth); + } else { + layoutManager.setNeedsMeasure(connector); + } + } break; case ORIENTATION_VERTICAL: wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight"); @@ -463,34 +351,50 @@ public class VSplitPanel extends ComplexPanel implements Container, DOM.setStyleAttribute(secondContainer, "top", (pixelPosition + getSplitterSize()) + "px"); - int contentWidth = renderInformation.getRenderedSize().getWidth(); - firstRenderSpace.setHeight(pixelPosition); - firstRenderSpace.setWidth(contentWidth); - secondRenderSpace.setHeight(secondContainerHeight); - secondRenderSpace.setWidth(contentWidth); - + layoutManager = LayoutManager.get(client); + connectorMap = ConnectorMap.get(client); + if (firstChild != null) { + ComponentConnector connector = connectorMap + .getConnector(firstChild); + if (connector.isRelativeHeight()) { + layoutManager.reportHeightAssignedToRelative(connector, + pixelPosition); + } else { + layoutManager.setNeedsMeasure(connector); + } + } + if (secondChild != null) { + ComponentConnector connector = connectorMap + .getConnector(secondChild); + if (connector.isRelativeHeight()) { + layoutManager.reportHeightAssignedToRelative(connector, + secondContainerHeight); + } else { + layoutManager.setNeedsMeasure(connector); + } + } break; } - // fixes scrollbars issues on webkit based browsers - Util.runWebkitOverflowAutoFix(secondContainer); - Util.runWebkitOverflowAutoFix(firstContainer); - } - private void setFirstWidget(Widget w) { + void setFirstWidget(Widget w) { if (firstChild != null) { firstChild.removeFromParent(); } - super.add(w, firstContainer); + if (w != null) { + super.add(w, firstContainer); + } firstChild = w; } - private void setSecondWidget(Widget w) { + void setSecondWidget(Widget w) { if (secondChild != null) { secondChild.removeFromParent(); } - super.add(w, secondContainer); + if (w != null) { + super.add(w, secondContainer); + } secondChild = w; } @@ -658,7 +562,38 @@ public class VSplitPanel extends ComplexPanel implements Container, if (!Util.isTouchEvent(event)) { onMouseMove(event); } - updateSplitPositionToServer(); + fireEvent(new SplitterMoveEvent(this)); + } + + public interface SplitterMoveHandler extends EventHandler { + public void splitterMoved(SplitterMoveEvent event); + + public static class SplitterMoveEvent extends + GwtEvent<SplitterMoveHandler> { + + public static final Type<SplitterMoveHandler> TYPE = new Type<SplitterMoveHandler>(); + + private Widget splitPanel; + + public SplitterMoveEvent(Widget splitPanel) { + this.splitPanel = splitPanel; + } + + @Override + public com.google.gwt.event.shared.GwtEvent.Type<SplitterMoveHandler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(SplitterMoveHandler handler) { + handler.splitterMoved(this); + } + + } + } + + String getSplitterPosition() { + return position; } /** @@ -723,98 +658,7 @@ public class VSplitPanel extends ComplexPanel implements Container, return splitterSize; } - @Override - public void setHeight(String height) { - if (this.height.equals(height)) { - return; - } - - this.height = height; - super.setHeight(height); - - if (!rendering && client != null) { - setSplitPosition(position); - } - } - - @Override - public void setWidth(String width) { - if (this.width.equals(width)) { - return; - } - - this.width = width; - super.setWidth(width); - - if (!rendering && client != null) { - setSplitPosition(position); - } - } - - public RenderSpace getAllocatedSpace(Widget child) { - if (child == firstChild) { - return firstRenderSpace; - } else if (child == secondChild) { - return secondRenderSpace; - } - - return null; - } - - public boolean hasChildComponent(Widget component) { - return (component != null && (component == firstChild || component == secondChild)); - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - if (oldComponent == firstChild) { - setFirstWidget(newComponent); - } else if (oldComponent == secondChild) { - setSecondWidget(newComponent); - } - } - - public boolean requestLayout(Set<Paintable> child) { - // content size change might cause change to its available space - // (scrollbars) - for (Paintable paintable : child) { - client.handleComponentRelativeSize((Widget) paintable); - } - if (height != null && width != null) { - /* - * If the height and width has been specified the child components - * cannot make the size of the layout change - */ - - return true; - } - - if (renderInformation.updateSize(getElement())) { - return false; - } else { - return true; - } - - } - - public void updateCaption(Paintable component, UIDL uidl) { - // TODO Implement caption handling - } - - /** - * Updates the new split position back to server. - */ - private void updateSplitPositionToServer() { - float pos = 0; - if (position.indexOf("%") > 0) { - pos = Float.valueOf(position.substring(0, position.length() - 1)); - } else { - pos = Integer - .parseInt(position.substring(0, position.length() - 2)); - } - client.updateVariable(id, "position", pos, immediate); - } - - private void setStylenames() { + void setStylenames() { final String splitterSuffix = (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" : "-vsplitter"); final String firstContainerSuffix = "-first-container"; @@ -829,13 +673,12 @@ public class VSplitPanel extends ComplexPanel implements Container, splitterStyle = CLASSNAME + splitterSuffix + "-locked"; lockedSuffix = "-locked"; } - for (int i = 0; i < componentStyleNames.length; i++) { - splitterStyle += " " + CLASSNAME + splitterSuffix + "-" - + componentStyleNames[i] + lockedSuffix; - firstStyle += " " + CLASSNAME + firstContainerSuffix + "-" - + componentStyleNames[i]; + for (String style : componentStyleNames) { + splitterStyle += " " + CLASSNAME + splitterSuffix + "-" + style + + lockedSuffix; + firstStyle += " " + CLASSNAME + firstContainerSuffix + "-" + style; secondStyle += " " + CLASSNAME + secondContainerSuffix + "-" - + componentStyleNames[i]; + + style; } DOM.setElementProperty(splitter, "className", splitterStyle); DOM.setElementProperty(firstContainer, "className", firstStyle); @@ -849,4 +692,5 @@ public class VSplitPanel extends ComplexPanel implements Container, public boolean isEnabled() { return enabled; } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelHorizontal.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelHorizontal.java new file mode 100644 index 0000000000..9048a59d7d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelHorizontal.java @@ -0,0 +1,12 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +public class VSplitPanelHorizontal extends VAbstractSplitPanel { + + public VSplitPanelHorizontal() { + super(VAbstractSplitPanel.ORIENTATION_HORIZONTAL); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelVertical.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelVertical.java new file mode 100644 index 0000000000..d22ebed5d9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelVertical.java @@ -0,0 +1,12 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +public class VSplitPanelVertical extends VAbstractSplitPanel { + + public VSplitPanelVertical() { + super(VAbstractSplitPanel.ORIENTATION_VERTICAL); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VerticalSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VerticalSplitPanelConnector.java new file mode 100644 index 0000000000..83404177c0 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VerticalSplitPanelConnector.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.VerticalSplitPanel; + +@Connect(value = VerticalSplitPanel.class, loadStyle = LoadStyle.EAGER) +public class VerticalSplitPanelConnector extends AbstractSplitPanelConnector { + + @Override + protected VAbstractSplitPanel createWidget() { + return GWT.create(VSplitPanelVertical.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java b/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java new file mode 100644 index 0000000000..f16ee3463f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java @@ -0,0 +1,333 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.table; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.ContextMenuDetails; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow; + +@Connect(com.vaadin.ui.Table.class) +public class TableConnector extends AbstractComponentContainerConnector + implements Paintable, DirectionalManagedLayout, PostLayoutListener { + + @Override + protected void init() { + super.init(); + getWidget().init(getConnection()); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal + * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection) + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().rendering = true; + + // If a row has an open context menu, it will be closed as the row is + // detached. Retain a reference here so we can restore the menu if + // required. + ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu; + + if (uidl.hasAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST)) { + getWidget().serverCacheFirst = uidl + .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST); + getWidget().serverCacheLast = uidl + .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_LAST); + } else { + getWidget().serverCacheFirst = -1; + getWidget().serverCacheLast = -1; + } + /* + * We need to do this before updateComponent since updateComponent calls + * this.setHeight() which will calculate a new body height depending on + * the space available. + */ + if (uidl.hasAttribute("colfooters")) { + getWidget().showColFooters = uidl.getBooleanAttribute("colfooters"); + } + + getWidget().tFoot.setVisible(getWidget().showColFooters); + + if (!isRealUpdate(uidl)) { + getWidget().rendering = false; + return; + } + + getWidget().enabled = isEnabled(); + + if (BrowserInfo.get().isIE8() && !getWidget().enabled) { + /* + * The disabled shim will not cover the table body if it is relative + * in IE8. See #7324 + */ + getWidget().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.STATIC); + } else if (BrowserInfo.get().isIE8()) { + getWidget().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.RELATIVE); + } + + getWidget().paintableId = uidl.getStringAttribute("id"); + getWidget().immediate = getState().isImmediate(); + + int previousTotalRows = getWidget().totalRows; + getWidget().updateTotalRows(uidl); + boolean totalRowsChanged = (getWidget().totalRows != previousTotalRows); + + getWidget().updateDragMode(uidl); + + getWidget().updateSelectionProperties(uidl, getState(), isReadOnly()); + + if (uidl.hasAttribute("alb")) { + getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); + } else { + // Need to clear the actions if the action handlers have been + // removed + getWidget().bodyActionKeys = null; + } + + getWidget().setCacheRateFromUIDL(uidl); + + getWidget().recalcWidths = uidl.hasAttribute("recalcWidths"); + if (getWidget().recalcWidths) { + getWidget().tHead.clear(); + getWidget().tFoot.clear(); + } + + getWidget().updatePageLength(uidl); + + getWidget().updateFirstVisibleAndScrollIfNeeded(uidl); + + getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders"); + getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders"); + + getWidget().updateSortingProperties(uidl); + + boolean keyboardSelectionOverRowFetchInProgress = getWidget() + .selectSelectedRows(uidl); + + getWidget().updateActionMap(uidl); + + getWidget().updateColumnProperties(uidl); + + UIDL ac = uidl.getChildByTagName("-ac"); + if (ac == null) { + if (getWidget().dropHandler != null) { + // remove dropHandler if not present anymore + getWidget().dropHandler = null; + } + } else { + if (getWidget().dropHandler == null) { + getWidget().dropHandler = getWidget().new VScrollTableDropHandler(); + } + getWidget().dropHandler.updateAcceptRules(ac); + } + + UIDL partialRowAdditions = uidl.getChildByTagName("prows"); + UIDL partialRowUpdates = uidl.getChildByTagName("urows"); + if (partialRowUpdates != null || partialRowAdditions != null) { + // we may have pending cache row fetch, cancel it. See #2136 + getWidget().rowRequestHandler.cancel(); + + getWidget().updateRowsInBody(partialRowUpdates); + getWidget().addAndRemoveRows(partialRowAdditions); + } else { + UIDL rowData = uidl.getChildByTagName("rows"); + if (rowData != null) { + // we may have pending cache row fetch, cancel it. See #2136 + getWidget().rowRequestHandler.cancel(); + + if (!getWidget().recalcWidths + && getWidget().initializedAndAttached) { + getWidget().updateBody(rowData, + uidl.getIntAttribute("firstrow"), + uidl.getIntAttribute("rows")); + if (getWidget().headerChangedDuringUpdate) { + getWidget().triggerLazyColumnAdjustment(true); + } else if (!getWidget().isScrollPositionVisible() + || totalRowsChanged + || getWidget().lastRenderedHeight != getWidget().scrollBody + .getOffsetHeight()) { + // webkits may still bug with their disturbing scrollbar + // bug, see #3457 + // Run overflow fix for the scrollable area + // #6698 - If there's a scroll going on, don't abort it + // by changing overflows as the length of the contents + // *shouldn't* have changed (unless the number of rows + // or the height of the widget has also changed) + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + Util.runWebkitOverflowAutoFix(getWidget().scrollBodyPanel + .getElement()); + } + }); + } + } else { + getWidget().initializeRows(uidl, rowData); + } + } + } + + // If a row had an open context menu before the update, and after the + // update there's a row with the same key as that row, restore the + // context menu. See #8526. + showSavedContextMenu(contextMenuBeforeUpdate); + + if (!getWidget().isSelectable()) { + getWidget().scrollBody.addStyleName(VScrollTable.CLASSNAME + + "-body-noselection"); + } else { + getWidget().scrollBody.removeStyleName(VScrollTable.CLASSNAME + + "-body-noselection"); + } + + getWidget().hideScrollPositionAnnotation(); + + // selection is no in sync with server, avoid excessive server visits by + // clearing to flag used during the normal operation + if (!keyboardSelectionOverRowFetchInProgress) { + getWidget().selectionChanged = false; + } + + /* + * This is called when the Home or page up button has been pressed in + * selectable mode and the next selected row was not yet rendered in the + * client + */ + if (getWidget().selectFirstItemInNextRender + || getWidget().focusFirstItemInNextRender) { + getWidget().selectFirstRenderedRowInViewPort( + getWidget().focusFirstItemInNextRender); + getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false; + } + + /* + * This is called when the page down or end button has been pressed in + * selectable mode and the next selected row was not yet rendered in the + * client + */ + if (getWidget().selectLastItemInNextRender + || getWidget().focusLastItemInNextRender) { + getWidget().selectLastRenderedRowInViewPort( + getWidget().focusLastItemInNextRender); + getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false; + } + getWidget().multiselectPending = false; + + if (getWidget().focusedRow != null) { + if (!getWidget().focusedRow.isAttached() + && !getWidget().rowRequestHandler.isRunning()) { + // focused row has been orphaned, can't focus + getWidget().focusRowFromBody(); + } + } + + getWidget().tabIndex = uidl.hasAttribute("tabindex") ? uidl + .getIntAttribute("tabindex") : 0; + getWidget().setProperTabIndex(); + + getWidget().resizeSortedColumnForSortIndicator(); + + // Remember this to detect situations where overflow hack might be + // needed during scrolling + getWidget().lastRenderedHeight = getWidget().scrollBody + .getOffsetHeight(); + + getWidget().rendering = false; + getWidget().headerChangedDuringUpdate = false; + + } + + @Override + protected Widget createWidget() { + return GWT.create(VScrollTable.class); + } + + @Override + public VScrollTable getWidget() { + return (VScrollTable) super.getWidget(); + } + + public void updateCaption(ComponentConnector component) { + // NOP, not rendered + } + + public void layoutVertically() { + getWidget().updateHeight(); + } + + public void layoutHorizontally() { + getWidget().updateWidth(); + } + + public void postLayout() { + VScrollTable table = getWidget(); + if (table.sizeNeedsInit) { + table.sizeInit(); + Scheduler.get().scheduleFinally(new ScheduledCommand() { + public void execute() { + getLayoutManager().setNeedsMeasure(TableConnector.this); + getLayoutManager().setNeedsMeasure( + TableConnector.this.getParent()); + getLayoutManager().setNeedsVerticalLayout( + TableConnector.this); + getLayoutManager().layoutNow(); + } + }); + } + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().isPropertyReadOnly(); + } + + @Override + public AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + + /** + * Shows a saved row context menu if the row for the context menu is still + * visible. Does nothing if a context menu has not been saved. + * + * @param savedContextMenu + */ + public void showSavedContextMenu(ContextMenuDetails savedContextMenu) { + if (isEnabled() && savedContextMenu != null) { + Iterator<Widget> iterator = getWidget().scrollBody.iterator(); + while (iterator.hasNext()) { + Widget w = iterator.next(); + VScrollTableRow row = (VScrollTableRow) w; + if (row.getKey().equals(savedContextMenu.rowKey)) { + getWidget().contextMenu = savedContextMenu; + getConnection().getContextMenu().showAt(row, + savedContextMenu.left, savedContextMenu.top); + } + } + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java index a022a2bd83..c45c26c4ac 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.table; import java.util.ArrayList; import java.util.Collection; @@ -55,6 +55,7 @@ import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.RootPanel; @@ -62,17 +63,22 @@ import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Container; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.Focusable; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; import com.vaadin.terminal.gwt.client.TooltipInfo; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.terminal.gwt.client.ui.Action; +import com.vaadin.terminal.gwt.client.ui.ActionOwner; +import com.vaadin.terminal.gwt.client.ui.FocusableScrollPanel; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TreeAction; import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; @@ -81,6 +87,10 @@ import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent; import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler; import com.vaadin.terminal.gwt.client.ui.dd.VTransferable; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; +import com.vaadin.terminal.gwt.client.ui.embedded.VEmbedded; +import com.vaadin.terminal.gwt.client.ui.label.VLabel; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; /** * VScrollTable @@ -105,17 +115,31 @@ import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; * * TODO implement unregistering for child components in Cells */ -public class VScrollTable extends FlowPanel implements Table, ScrollHandler, - VHasDropHandler, FocusHandler, BlurHandler, Focusable, ActionOwner { +public class VScrollTable extends FlowPanel implements HasWidgets, + ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, + ActionOwner { - public static final String ATTRIBUTE_PAGEBUFFER_FIRST = "pb-ft"; - public static final String ATTRIBUTE_PAGEBUFFER_LAST = "pb-l"; + public enum SelectMode { + NONE(0), SINGLE(1), MULTI(2); + private int id; + + private SelectMode(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } private static final String ROW_HEADER_COLUMN_KEY = "0"; public static final String CLASSNAME = "v-table"; public static final String CLASSNAME_SELECTION_FOCUS = CLASSNAME + "-focus"; + public static final String ATTRIBUTE_PAGEBUFFER_FIRST = "pb-ft"; + public static final String ATTRIBUTE_PAGEBUFFER_LAST = "pb-l"; + public static final String ITEM_CLICK_EVENT_ID = "itemClick"; public static final String HEADER_CLICK_EVENT_ID = "handleHeaderClick"; public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick"; @@ -164,10 +188,10 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, protected ApplicationConnection client; protected String paintableId; - private boolean immediate; + boolean immediate; private boolean nullSelectionAllowed = true; - private int selectMode = Table.SELECT_MODE_NONE; + private SelectMode selectMode = SelectMode.NONE; private final HashSet<String> selectedRowKeys = new HashSet<String>(); @@ -180,15 +204,15 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, /* * These are used when jumping between pages when pressing Home and End */ - private boolean selectLastItemInNextRender = false; - private boolean selectFirstItemInNextRender = false; - private boolean focusFirstItemInNextRender = false; - private boolean focusLastItemInNextRender = false; + boolean selectLastItemInNextRender = false; + boolean selectFirstItemInNextRender = false; + boolean focusFirstItemInNextRender = false; + boolean focusLastItemInNextRender = false; /* * The currently focused row */ - private VScrollTableRow focusedRow; + VScrollTableRow focusedRow; /* * Helper to store selection range start in when using the keyboard @@ -199,7 +223,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * Flag for notifying when the selection has changed and should be sent to * the server */ - private boolean selectionChanged = false; + boolean selectionChanged = false; /* * The speed (in pixels) which the scrolling scrolls vertically/horizontally @@ -208,7 +232,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private Timer scrollingVelocityTimer = null; - private String[] bodyActionKeys; + String[] bodyActionKeys; private boolean enableDebug = false; @@ -283,19 +307,18 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>(); - private boolean initializedAndAttached = false; + boolean initializedAndAttached = false; /** * Flag to indicate if a column width recalculation is needed due update. */ - private boolean headerChangedDuringUpdate = false; + boolean headerChangedDuringUpdate = false; protected final TableHead tHead = new TableHead(); - private final TableFooter tFoot = new TableFooter(); + final TableFooter tFoot = new TableFooter(); - private final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel( - true); + final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(true); private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { public void onKeyPress(KeyPressEvent keyPressEvent) { @@ -384,12 +407,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } }; - private int totalRows; + int totalRows; private Set<String> collapsedColumns; - private final RowRequestHandler rowRequestHandler; - private VScrollTableBody scrollBody; + final RowRequestHandler rowRequestHandler; + VScrollTableBody scrollBody; private int firstvisible = 0; private boolean sortAscending; private String sortColumn; @@ -404,9 +427,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private String[] visibleColOrder; private boolean initialContentReceived = false; private Element scrollPositionElement; - private boolean enabled; - private boolean showColHeaders; - private boolean showColFooters; + boolean enabled; + boolean showColHeaders; + boolean showColFooters; /** flag to indicate that table body has changed */ private boolean isNewBody = true; @@ -418,18 +441,15 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, */ boolean recalcWidths = false; - private final ArrayList<Panel> lazyUnregistryBag = new ArrayList<Panel>(); - private String height; - private String width = ""; - private boolean rendering = false; + boolean rendering = false; private boolean hasFocus = false; private int dragmode; private int multiselectmode; - private int tabIndex; + int tabIndex; private TouchScrollDelegate touchScrollDelegate; - private int lastRenderedHeight; + int lastRenderedHeight; /** * Values (serverCacheFirst+serverCacheLast) sent by server that tells which @@ -443,14 +463,16 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * scrolling in the client will cause empty buttons to be rendered * (cached=true request for non-existing components) */ - private int serverCacheFirst = -1; - private int serverCacheLast = -1; + int serverCacheFirst = -1; + int serverCacheLast = -1; + + boolean sizeNeedsInit = true; /** * Used to recall the position of an open context menu if we need to close * and reopen it during a row update. */ - private class ContextMenuDetails { + class ContextMenuDetails { String rowKey; int left; int top; @@ -462,7 +484,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - ContextMenuDetails contextMenu; + protected ContextMenuDetails contextMenu = null; public VScrollTable() { setMultiSelectMode(MULTISELECT_MODE_DEFAULT); @@ -509,6 +531,17 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, rowRequestHandler = new RowRequestHandler(); } + public void init(ApplicationConnection client) { + this.client = client; + // Add a handler to clear saved context menu details when the menu + // closes. See #8526. + client.getContextMenu().addCloseHandler(new CloseHandler<PopupPanel>() { + public void onClose(CloseEvent<PopupPanel> event) { + contextMenu = null; + } + }); + } + protected TouchScrollDelegate getTouchScrollDelegate() { if (touchScrollDelegate == null) { touchScrollDelegate = new TouchScrollDelegate( @@ -830,243 +863,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return KeyCodes.KEY_END; } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal - * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - - // On the first rendering, add a handler to clear saved context menu - // details when the menu closes. See #8526. - if (this.client == null) { - client.getContextMenu().addCloseHandler( - new CloseHandler<PopupPanel>() { - public void onClose(CloseEvent<PopupPanel> event) { - contextMenu = null; - } - }); - } - // If a row has an open context menu, it will be closed as the row is - // detached. Retain a reference here so we can restore the menu if - // required. - ContextMenuDetails savedContextMenu = contextMenu; - - if (uidl.hasAttribute(ATTRIBUTE_PAGEBUFFER_FIRST)) { - serverCacheFirst = uidl.getIntAttribute(ATTRIBUTE_PAGEBUFFER_FIRST); - serverCacheLast = uidl.getIntAttribute(ATTRIBUTE_PAGEBUFFER_LAST); - } else { - serverCacheFirst = -1; - serverCacheLast = -1; - } - /* - * We need to do this before updateComponent since updateComponent calls - * this.setHeight() which will calculate a new body height depending on - * the space available. - */ - if (uidl.hasAttribute("colfooters")) { - showColFooters = uidl.getBooleanAttribute("colfooters"); - } - - tFoot.setVisible(showColFooters); - - if (client.updateComponent(this, uidl, true)) { - rendering = false; - return; - } - - enabled = !uidl.hasAttribute("disabled"); - - if (BrowserInfo.get().isIE8() && !enabled) { - /* - * The disabled shim will not cover the table body if it is relative - * in IE8. See #7324 - */ - scrollBodyPanel.getElement().getStyle() - .setPosition(Position.STATIC); - } else if (BrowserInfo.get().isIE8()) { - scrollBodyPanel.getElement().getStyle() - .setPosition(Position.RELATIVE); - } - - this.client = client; - paintableId = uidl.getStringAttribute("id"); - immediate = uidl.getBooleanAttribute("immediate"); - - int previousTotalRows = totalRows; - updateTotalRows(uidl); - boolean totalRowsChanged = (totalRows != previousTotalRows); - - updateDragMode(uidl); - - updateSelectionProperties(uidl); - - if (uidl.hasAttribute("alb")) { - bodyActionKeys = uidl.getStringArrayAttribute("alb"); - } else { - // Need to clear the actions if the action handlers have been - // removed - bodyActionKeys = null; - } - - setCacheRateFromUIDL(uidl); - - recalcWidths = uidl.hasAttribute("recalcWidths"); - if (recalcWidths) { - tHead.clear(); - tFoot.clear(); - } - - updatePageLength(uidl); - - updateFirstVisibleAndScrollIfNeeded(uidl); - - showRowHeaders = uidl.getBooleanAttribute("rowheaders"); - showColHeaders = uidl.getBooleanAttribute("colheaders"); - - updateSortingProperties(uidl); - - boolean keyboardSelectionOverRowFetchInProgress = selectSelectedRows(uidl); - - updateActionMap(uidl); - - updateColumnProperties(uidl); - - UIDL ac = uidl.getChildByTagName("-ac"); - if (ac == null) { - if (dropHandler != null) { - // remove dropHandler if not present anymore - dropHandler = null; - } - } else { - if (dropHandler == null) { - dropHandler = new VScrollTableDropHandler(); - } - dropHandler.updateAcceptRules(ac); - } - - UIDL partialRowAdditions = uidl.getChildByTagName("prows"); - UIDL partialRowUpdates = uidl.getChildByTagName("urows"); - if (partialRowUpdates != null || partialRowAdditions != null) { - // we may have pending cache row fetch, cancel it. See #2136 - rowRequestHandler.cancel(); - - updateRowsInBody(partialRowUpdates); - addAndRemoveRows(partialRowAdditions); - } else { - UIDL rowData = uidl.getChildByTagName("rows"); - if (rowData != null) { - // we may have pending cache row fetch, cancel it. See #2136 - rowRequestHandler.cancel(); - - if (!recalcWidths && initializedAndAttached) { - updateBody(rowData, uidl.getIntAttribute("firstrow"), - uidl.getIntAttribute("rows")); - if (headerChangedDuringUpdate) { - triggerLazyColumnAdjustment(true); - } else if (!isScrollPositionVisible() - || totalRowsChanged - || lastRenderedHeight != scrollBody - .getOffsetHeight()) { - // webkits may still bug with their disturbing scrollbar - // bug, see #3457 - // Run overflow fix for the scrollable area - // #6698 - If there's a scroll going on, don't abort it - // by changing overflows as the length of the contents - // *shouldn't* have changed (unless the number of rows - // or the height of the widget has also changed) - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - Util.runWebkitOverflowAutoFix(scrollBodyPanel - .getElement()); - } - }); - } - } else { - initializeRows(uidl, rowData); - } - } - } - - // If a row had an open context menu before the update, and after the - // update there's a row with the same key as that row, restore the - // context menu. See #8526. - if (enabled && savedContextMenu != null) { - for (Widget w : scrollBody.renderedRows) { - VScrollTableRow row = (VScrollTableRow) w; - if (row.isVisible() - && row.getKey().equals(savedContextMenu.rowKey)) { - contextMenu = savedContextMenu; - client.getContextMenu().showAt(row, savedContextMenu.left, - savedContextMenu.top); - } - } - } - - if (!isSelectable()) { - scrollBody.addStyleName(CLASSNAME + "-body-noselection"); - } else { - scrollBody.removeStyleName(CLASSNAME + "-body-noselection"); - } - - hideScrollPositionAnnotation(); - purgeUnregistryBag(); - - // selection is no in sync with server, avoid excessive server visits by - // clearing to flag used during the normal operation - if (!keyboardSelectionOverRowFetchInProgress) { - selectionChanged = false; - } - - /* - * This is called when the Home or page up button has been pressed in - * selectable mode and the next selected row was not yet rendered in the - * client - */ - if (selectFirstItemInNextRender || focusFirstItemInNextRender) { - selectFirstRenderedRowInViewPort(focusFirstItemInNextRender); - selectFirstItemInNextRender = focusFirstItemInNextRender = false; - } - - /* - * This is called when the page down or end button has been pressed in - * selectable mode and the next selected row was not yet rendered in the - * client - */ - if (selectLastItemInNextRender || focusLastItemInNextRender) { - selectLastRenderedRowInViewPort(focusLastItemInNextRender); - selectLastItemInNextRender = focusLastItemInNextRender = false; - } - multiselectPending = false; - - if (focusedRow != null) { - if (!focusedRow.isAttached() && !rowRequestHandler.isRunning()) { - // focused row has been orphaned, can't focus - focusRowFromBody(); - } - } - - tabIndex = uidl.hasAttribute("tabindex") ? uidl - .getIntAttribute("tabindex") : 0; - setProperTabIndex(); - - resizeSortedColumnForSortIndicator(); - - // Remember this to detect situations where overflow hack might be - // needed during scrolling - lastRenderedHeight = scrollBody.getOffsetHeight(); - - rendering = false; - headerChangedDuringUpdate = false; - } - - private void initializeRows(UIDL uidl, UIDL rowData) { + void initializeRows(UIDL uidl, UIDL rowData) { if (scrollBody != null) { scrollBody.removeFromParent(); - lazyUnregistryBag.add(scrollBody); } scrollBody = createScrollBody(); @@ -1080,13 +879,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, tFoot.setHorizontalScrollPosition(0); initialContentReceived = true; - if (isAttached()) { - sizeInit(); - } + sizeNeedsInit = true; scrollBody.restoreRowVisibility(); } - private void updateColumnProperties(UIDL uidl) { + void updateColumnProperties(UIDL uidl) { updateColumnOrder(uidl); updateCollapsedColumns(uidl); @@ -1121,7 +918,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - private boolean selectSelectedRows(UIDL uidl) { + boolean selectSelectedRows(UIDL uidl) { boolean keyboardSelectionOverRowFetchInProgress = false; if (uidl.hasVariable("selected")) { @@ -1158,7 +955,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return keyboardSelectionOverRowFetchInProgress; } - private void updateSortingProperties(UIDL uidl) { + void updateSortingProperties(UIDL uidl) { oldSortColumn = sortColumn; if (uidl.hasVariable("sortascending")) { sortAscending = uidl.getBooleanVariable("sortascending"); @@ -1166,7 +963,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - private void resizeSortedColumnForSortIndicator() { + void resizeSortedColumnForSortIndicator() { // Force recalculation of the captionContainer element inside the header // cell to accomodate for the size of the sort arrow. HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn); @@ -1181,7 +978,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - private void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) { + void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) { firstvisible = uidl.hasVariable("firstvisible") ? uidl .getIntVariable("firstvisible") : 0; if (firstvisible != lastRequestedFirstvisible && scrollBody != null) { @@ -1196,7 +993,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return (int) (rowIx * scrollBody.getRowHeight()); } - private void updatePageLength(UIDL uidl) { + void updatePageLength(UIDL uidl) { int oldPageLength = pageLength; if (uidl.hasAttribute("pagelength")) { pageLength = uidl.getIntAttribute("pagelength"); @@ -1207,11 +1004,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (oldPageLength != pageLength && initializedAndAttached) { // page length changed, need to update size - sizeInit(); + sizeNeedsInit = true; } } - private void updateSelectionProperties(UIDL uidl) { + void updateSelectionProperties(UIDL uidl, ComponentState state, + boolean readOnly) { setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT); @@ -1219,19 +1017,19 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, .getBooleanAttribute("nsa") : true; if (uidl.hasAttribute("selectmode")) { - if (uidl.getBooleanAttribute("readonly")) { - selectMode = Table.SELECT_MODE_NONE; + if (readOnly) { + selectMode = SelectMode.NONE; } else if (uidl.getStringAttribute("selectmode").equals("multi")) { - selectMode = Table.SELECT_MODE_MULTI; + selectMode = SelectMode.MULTI; } else if (uidl.getStringAttribute("selectmode").equals("single")) { - selectMode = Table.SELECT_MODE_SINGLE; + selectMode = SelectMode.SINGLE; } else { - selectMode = Table.SELECT_MODE_NONE; + selectMode = SelectMode.NONE; } } } - private void updateDragMode(UIDL uidl) { + void updateDragMode(UIDL uidl) { dragmode = uidl.hasAttribute("dragmode") ? uidl .getIntAttribute("dragmode") : 0; if (BrowserInfo.get().isIE()) { @@ -1264,11 +1062,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, totalRows = newTotalRows; } - protected int getTotalRows() { + public int getTotalRows() { return totalRows; } - private void focusRowFromBody() { + void focusRowFromBody() { if (selectedRowKeys.size() == 1) { // try to focus a row currently selected and in viewport String selectedRowKey = selectedRowKeys.iterator().next(); @@ -1296,7 +1094,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * @param focusOnly * Should the focus only be moved to the last row */ - private void selectLastRenderedRowInViewPort(boolean focusOnly) { + void selectLastRenderedRowInViewPort(boolean focusOnly) { int index = firstRowInViewPort + getFullyVisibleRowCount(); VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index); if (lastRowInViewport == null) { @@ -1321,7 +1119,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * @param focusOnly * Should the focus only be moved to the first row */ - private void selectFirstRenderedRowInViewPort(boolean focusOnly) { + void selectFirstRenderedRowInViewPort(boolean focusOnly) { int index = firstRowInViewPort; VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index); if (firstInViewport == null) { @@ -1335,7 +1133,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - private void setCacheRateFromUIDL(UIDL uidl) { + void setCacheRateFromUIDL(UIDL uidl) { setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr") : CACHE_RATE_DEFAULT); } @@ -1347,20 +1145,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - /** - * Unregisters Paintables in "trashed" HasWidgets (IScrollTableBodys or - * IScrollTableRows). This is done lazily as Table must survive from - * "subtreecaching" logic. - */ - private void purgeUnregistryBag() { - for (Iterator<Panel> iterator = lazyUnregistryBag.iterator(); iterator - .hasNext();) { - client.unregisterChildPaintables(iterator.next()); - } - lazyUnregistryBag.clear(); - } - - private void updateActionMap(UIDL mainUidl) { + void updateActionMap(UIDL mainUidl) { UIDL actionsUidl = mainUidl.getChildByTagName("actions"); if (actionsUidl == null) { return; @@ -1462,7 +1247,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * @param reqRows * amount of rows in data set */ - private void updateBody(UIDL uidl, int firstRow, int reqRows) { + void updateBody(UIDL uidl, int firstRow, int reqRows) { if (uidl == null || reqRows < 1) { // container is empty, remove possibly existing rows if (firstRow <= 0) { @@ -1479,7 +1264,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, discardRowsOutsideCacheWindow(); } - private void updateRowsInBody(UIDL partialRowUpdates) { + void updateRowsInBody(UIDL partialRowUpdates) { if (partialRowUpdates == null) { return; } @@ -1612,20 +1397,20 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } private boolean isMultiSelectModeSimple() { - return selectMode == Table.SELECT_MODE_MULTI + return selectMode == SelectMode.MULTI && multiselectmode == MULTISELECT_MODE_SIMPLE; } private boolean isSingleSelectMode() { - return selectMode == Table.SELECT_MODE_SINGLE; + return selectMode == SelectMode.SINGLE; } private boolean isMultiSelectModeAny() { - return selectMode == Table.SELECT_MODE_MULTI; + return selectMode == SelectMode.MULTI; } private boolean isMultiSelectModeDefault() { - return selectMode == Table.SELECT_MODE_MULTI + return selectMode == SelectMode.MULTI && multiselectmode == MULTISELECT_MODE_DEFAULT; } @@ -1641,7 +1426,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } protected boolean isSelectable() { - return selectMode > Table.SELECT_MODE_NONE; + return selectMode.getId() > SelectMode.NONE.getId(); } private boolean isCollapsedColumn(String colKey) { @@ -1692,7 +1477,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The key to search with * @return */ - protected VScrollTableRow getRenderedRowByKey(String key) { + public VScrollTableRow getRenderedRowByKey(String key) { if (scrollBody != null) { final Iterator<Widget> it = scrollBody.iterator(); VScrollTableRow r = null; @@ -1823,14 +1608,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } @Override - protected void onAttach() { - super.onAttach(); - if (initialContentReceived) { - sizeInit(); - } - } - - @Override protected void onDetach() { rowRequestHandler.cancel(); super.onDetach(); @@ -1853,7 +1630,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * * * Makes deferred request to get some cache rows */ - private void sizeInit() { + void sizeInit() { + sizeNeedsInit = false; + + scrollBody.setContainerHeight(); + /* * We will use browsers table rendering algorithm to find proper column * widths. If content and header take less space than available, we will @@ -1908,7 +1689,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, boolean willHaveScrollbarz = willHaveScrollbars(); // fix "natural" width if width not set - if (width == null || "".equals(width)) { + if (isDynamicWidth()) { int w = total; w += scrollBody.getCellExtraWidth() * visibleColOrder.length; if (willHaveScrollbarz) { @@ -2036,7 +1817,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * Fix "natural" height if height is not set. This must be after width * fixing so the components' widths have been adjusted. */ - if (height == null || "".equals(height)) { + if (isDynamicHeight()) { /* * We must force an update of the row height as this point as it * might have been (incorrectly) calculated earlier @@ -2120,7 +1901,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * @return true if content area will have scrollbars visible. */ protected boolean willHaveScrollbars() { - if (!(height != null && !height.equals(""))) { + if (isDynamicHeight()) { if (pageLength < totalRows) { return true; } @@ -2159,19 +1940,19 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, style.setDisplay(Display.BLOCK); } - private void hideScrollPositionAnnotation() { + void hideScrollPositionAnnotation() { if (scrollPositionElement != null) { DOM.setStyleAttribute(scrollPositionElement, "display", "none"); } } - private boolean isScrollPositionVisible() { + boolean isScrollPositionVisible() { return scrollPositionElement != null && !scrollPositionElement.getStyle().getDisplay() .equals(Display.NONE.toString()); } - private class RowRequestHandler extends Timer { + class RowRequestHandler extends Timer { private int reqFirstRow = 0; private int reqRows = 0; @@ -2346,11 +2127,10 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * of the caption container element by the correct amount */ public void resizeCaptionContainer(int rightSpacing) { - int captionContainerWidth = width - colResizeWidget.getOffsetWidth() - rightSpacing; - if (BrowserInfo.get().isIE6() || td.getClassName().contains("-asc") + if (td.getClassName().contains("-asc") || td.getClassName().contains("-desc")) { // Leave room for the sort indicator captionContainerWidth -= sortIndicator.getOffsetWidth(); @@ -2573,7 +2353,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private void fireHeaderClickedEvent(Event event) { if (client.hasEventListeners(VScrollTable.this, HEADER_CLICK_EVENT_ID)) { - MouseEventDetails details = new MouseEventDetails(event); + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); client.updateVariable(paintableId, "headerClickEvent", details.toString(), false); client.updateVariable(paintableId, "headerClickCID", cid, true); @@ -2830,8 +2611,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, int hw = captionContainer.getOffsetWidth() + scrollBody.getCellExtraWidth(); - if (BrowserInfo.get().isGecko() - || BrowserInfo.get().isIE7()) { + if (BrowserInfo.get().isGecko()) { hw += sortIndicator.getOffsetWidth(); } if (columnIndex < 0) { @@ -3098,12 +2878,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } public void setHorizontalScrollPosition(int scrollLeft) { - if (BrowserInfo.get().isIE6()) { - hTableWrapper.getStyle().setPosition(Position.RELATIVE); - hTableWrapper.getStyle().setLeft(-scrollLeft, Unit.PX); - } else { - hTableWrapper.setScrollLeft(scrollLeft); - } + hTableWrapper.setScrollLeft(scrollLeft); } public void setColumnCollapsingAllowed(boolean cc) { @@ -3636,7 +3411,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private void fireFooterClickedEvent(Event event) { if (client.hasEventListeners(VScrollTable.this, FOOTER_CLICK_EVENT_ID)) { - MouseEventDetails details = new MouseEventDetails(event); + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); client.updateVariable(paintableId, "footerClickEvent", details.toString(), false); client.updateVariable(paintableId, "footerClickCID", cid, true); @@ -3966,12 +3742,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The value of the leftScroll */ public void setHorizontalScrollPosition(int scrollLeft) { - if (BrowserInfo.get().isIE6()) { - hTableWrapper.getStyle().setProperty("position", "relative"); - hTableWrapper.getStyle().setPropertyPx("left", -scrollLeft); - } else { - hTableWrapper.setScrollLeft(scrollLeft); - } + hTableWrapper.setScrollLeft(scrollLeft); } /** @@ -4406,7 +4177,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, Element td = toBeRemoved.getElement().getChild(i).cast(); client.registerTooltip(VScrollTable.this, td, null); } - lazyUnregistryBag.add(toBeRemoved); tBodyElement.removeChild(toBeRemoved.getElement()); orphan(toBeRemoved); renderedRows.remove(index); @@ -4417,12 +4187,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, throw new UnsupportedOperationException(); } - @Override - protected void onAttach() { - super.onAttach(); - setContainerHeight(); - } - /** * Fix container blocks height according to totalRows to avoid * "bouncing" when scrolling @@ -4614,15 +4378,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - public class VScrollTableRow extends Panel implements ActionOwner, - Container { + public class VScrollTableRow extends Panel implements ActionOwner { private static final int TOUCHSCROLL_TIMEOUT = 100; private static final int DRAGMODE_MULTIROW = 2; protected ArrayList<Widget> childWidgets = new ArrayList<Widget>(); private boolean selected = false; protected final int rowKey; - private List<UIDL> pendingComponentPaints; private String[] actionKeys = null; private final TableRowElement rowElement; @@ -4744,12 +4506,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, addCell(uidl, cell.toString(), aligns[col++], style, isRenderHtmlInCells(), sorted, description); } else { - final Paintable cellContent = client + final ComponentConnector cellContent = client .getPaintable((UIDL) cell); - addCell(uidl, (Widget) cellContent, aligns[col++], + addCell(uidl, cellContent.getWidget(), aligns[col++], style, sorted); - paintComponent(cellContent, (UIDL) cell); } } } @@ -4822,29 +4583,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return index; } - protected void paintComponent(Paintable p, UIDL uidl) { - if (isAttached()) { - p.updateFromUIDL(uidl, client); - } else { - if (pendingComponentPaints == null) { - pendingComponentPaints = new LinkedList<UIDL>(); - } - pendingComponentPaints.add(uidl); - } - } - - @Override - protected void onAttach() { - super.onAttach(); - if (pendingComponentPaints != null) { - for (UIDL uidl : pendingComponentPaints) { - Paintable paintable = client.getPaintable(uidl); - paintable.updateFromUIDL(uidl, client); - } - pendingComponentPaints.clear(); - } - } - @Override protected void onDetach() { super.onDetach(); @@ -5003,7 +4741,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, false); } - MouseEventDetails details = new MouseEventDetails(event); + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); client.updateVariable(paintableId, "clickEvent", details.toString(), immediate); @@ -5027,8 +4766,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (!containsWidget) { // Only text nodes has tooltips - if (client.getTooltipTitleInfo(VScrollTable.this, - target) != null) { + if (ConnectorMap.get(client).getWidgetTooltipInfo( + VScrollTable.this, target) != null) { // Cell has description, use it client.handleTooltipEvent(event, VScrollTable.this, target); @@ -5361,7 +5100,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, Element targetTdOrTr) { mDown = true; VTransferable transferable = new VTransferable(); - transferable.setDragSource(VScrollTable.this); + transferable.setDragSource(ConnectorMap.get(client) + .getConnector(VScrollTable.this)); transferable.setData("itemId", "" + rowKey); NodeList<TableCellElement> cells = rowElement.getCells(); for (int i = 0; i < cells.getLength(); i++) { @@ -5580,29 +5320,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return paintableId; } - public RenderSpace getAllocatedSpace(Widget child) { - int w = 0; - int i = getColIndexOf(child); - HeaderCell headerCell = tHead.getHeaderCell(i); - if (headerCell != null) { - if (initializedAndAttached) { - w = headerCell.getWidth(); - } else { - // header offset width is not absolutely correct value, - // but a best guess (expecting similar content in all - // columns -> - // if one component is relative width so are others) - w = headerCell.getOffsetWidth() - getCellExtraWidth(); - } - } - return new RenderSpace(w, 0) { - @Override - public int getHeight() { - return (int) getRowHeight(); - } - }; - } - private int getColIndexOf(Widget child) { com.google.gwt.dom.client.Element widgetCell = child .getElement().getParentElement().getParentElement(); @@ -5615,37 +5332,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return -1; } - public boolean hasChildComponent(Widget component) { - return childWidgets.contains(component); - } - - public void replaceChildComponent(Widget oldComponent, - Widget newComponent) { - com.google.gwt.dom.client.Element parentElement = oldComponent - .getElement().getParentElement(); - int index = childWidgets.indexOf(oldComponent); - oldComponent.removeFromParent(); - - parentElement.appendChild(newComponent.getElement()); - childWidgets.add(index, newComponent); - adopt(newComponent); - - } - - public boolean requestLayout(Set<Paintable> children) { - // row size should never change and system wouldn't event - // survive as this is a kind of fake paitable - return true; - } - - public void updateCaption(Paintable component, UIDL uidl) { - // NOP, not rendered - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Should never be called, - // Component container interface faked here to get layouts - // render properly + public Widget getWidgetForPaintable() { + return this; } } @@ -5780,7 +5468,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (!hasFocus) { scrollBodyPanel.setFocus(true); } + } + } /** @@ -5822,7 +5512,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return; } - if (height == null || height.equals("")) { + if (isDynamicHeight()) { return; } @@ -5846,16 +5536,14 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, scrollBodyPanel.setScrollPosition(scrollTop + 1); scrollBodyPanel.setScrollPosition(scrollTop - 1); } + + sizeNeedsInit = true; } } } - @Override - public void setWidth(String width) { - if (this.width.equals(width)) { - return; - } + void updateWidth() { if (!isVisible()) { /* * Do not update size when the table is hidden as all column widths @@ -5865,9 +5553,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return; } - this.width = width; - if (width != null && !"".equals(width)) { - super.setWidth(width); + if (!isDynamicWidth()) { int innerPixels = getOffsetWidth() - getBorderWidth(); if (innerPixels < 0) { innerPixels = 0; @@ -5879,11 +5565,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } else { - // Undefined width - super.setWidth(""); - - // Readjust size of table - sizeInit(); + sizeNeedsInit = true; // readjust undefined width columns triggerLazyColumnAdjustment(false); @@ -5997,8 +5679,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - if ((height == null || "".equals(height)) - && totalRows == pageLength) { + if (isDynamicHeight() && totalRows == pageLength) { // fix body height (may vary if lazy loading is offhorizontal // scrollbar appears/disappears) int bodyHeight = scrollBody.getRequiredHeight(); @@ -6070,7 +5751,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private int containerHeight; private void setContainerHeight() { - if (height != null && !"".equals(height)) { + if (!isDynamicHeight()) { containerHeight = getOffsetHeight(); containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0; containerHeight -= tFoot.getOffsetHeight(); @@ -6085,41 +5766,46 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private int contentAreaBorderHeight = -1; private int scrollLeft; private int scrollTop; - private VScrollTableDropHandler dropHandler; + VScrollTableDropHandler dropHandler; private boolean navKeyDown; - private boolean multiselectPending; + boolean multiselectPending; /** * @return border top + border bottom of the scrollable area of table */ private int getContentAreaBorderHeight() { if (contentAreaBorderHeight < 0) { - if (BrowserInfo.get().isIE7() || BrowserInfo.get().isIE6()) { - contentAreaBorderHeight = Util - .measureVerticalBorder(scrollBodyPanel.getElement()); - } else { - DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", - "hidden"); - int oh = scrollBodyPanel.getOffsetHeight(); - int ch = scrollBodyPanel.getElement().getPropertyInt( - "clientHeight"); - contentAreaBorderHeight = oh - ch; - DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", - "auto"); - } + + DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", + "hidden"); + int oh = scrollBodyPanel.getOffsetHeight(); + int ch = scrollBodyPanel.getElement() + .getPropertyInt("clientHeight"); + contentAreaBorderHeight = oh - ch; + DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", + "auto"); } return contentAreaBorderHeight; } @Override public void setHeight(String height) { - this.height = height; + if (height.length() == 0 + && getElement().getStyle().getHeight().length() != 0) { + /* + * Changing from defined to undefined size -> should do a size init + * to take page length into account again + */ + sizeNeedsInit = true; + } super.setHeight(height); + } + + void updateHeight() { setContainerHeight(); - if (initializedAndAttached) { - updatePageLength(); - } + updatePageLength(); + if (!rendering) { // Webkit may sometimes get an odd rendering bug (white space // between header and body), see bug #3875. Running @@ -6439,8 +6125,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } @Override - public Paintable getPaintable() { - return VScrollTable.this; + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector(VScrollTable.this); } public ApplicationConnection getApplicationConnection() { @@ -6460,7 +6146,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The row to where the selection head should move * @return Returns true if focus was moved successfully, else false */ - protected boolean setRowFocus(VScrollTableRow row) { + public boolean setRowFocus(VScrollTableRow row) { if (!isSelectable()) { return false; @@ -6800,7 +6486,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (BrowserInfo.get().isIE()) { // IE sometimes moves focus to a clicked table cell... Element focusedElement = Util.getIEFocusedElement(); - if (Util.getPaintableForElement(client, getParent(), focusedElement) == this) { + if (Util.getConnectorForElement(client, getParent(), focusedElement) == this) { // ..in that case, steal the focus back to the focus handler // but not if focus is in a child component instead (#7965) focus(); @@ -6882,7 +6568,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * actions may need focus. * */ - private void setProperTabIndex() { + void setProperTabIndex() { int storedScrollTop = 0; int storedScrollLeft = 0; @@ -6991,7 +6677,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return function(){ return false; }; }-*/; - protected void triggerLazyColumnAdjustment(boolean now) { + public void triggerLazyColumnAdjustment(boolean now) { lazyAdjustColumnWidths.cancel(); if (now) { lazyAdjustColumnWidths.run(); @@ -7000,9 +6686,31 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } + private boolean isDynamicWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedWidth(); + } + + private boolean isDynamicHeight() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + if (paintable == null) { + // This should be refactored. As isDynamicHeight can be called from + // a timer it is possible that the connector has been unregistered + // when this method is called, causing getConnector to return null. + return false; + } + return paintable.isUndefinedHeight(); + } + private void debug(String msg) { if (enableDebug) { VConsole.error(msg); } } + + public Widget getWidgetForPaintable() { + return this; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetBaseConnector.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetBaseConnector.java new file mode 100644 index 0000000000..e16e84d112 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetBaseConnector.java @@ -0,0 +1,99 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; + +public abstract class TabsheetBaseConnector extends + AbstractComponentContainerConnector implements Paintable { + + public static final String ATTRIBUTE_TAB_DISABLED = "disabled"; + public static final String ATTRIBUTE_TAB_DESCRIPTION = "description"; + public static final String ATTRIBUTE_TAB_ERROR_MESSAGE = "error"; + public static final String ATTRIBUTE_TAB_CAPTION = "caption"; + public static final String ATTRIBUTE_TAB_ICON = "icon"; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + + if (!isRealUpdate(uidl)) { + return; + } + + // Update member references + getWidget().id = uidl.getId(); + getWidget().disabled = !isEnabled(); + + // Render content + final UIDL tabs = uidl.getChildUIDL(0); + + // Widgets in the TabSheet before update + ArrayList<Widget> oldWidgets = new ArrayList<Widget>(); + for (Iterator<Widget> iterator = getWidget().getWidgetIterator(); iterator + .hasNext();) { + oldWidgets.add(iterator.next()); + } + + // Clear previous values + getWidget().tabKeys.clear(); + getWidget().disabledTabKeys.clear(); + + int index = 0; + for (final Iterator<Object> it = tabs.getChildIterator(); it.hasNext();) { + final UIDL tab = (UIDL) it.next(); + final String key = tab.getStringAttribute("key"); + final boolean selected = tab.getBooleanAttribute("selected"); + final boolean hidden = tab.getBooleanAttribute("hidden"); + + if (tab.getBooleanAttribute(ATTRIBUTE_TAB_DISABLED)) { + getWidget().disabledTabKeys.add(key); + } + + getWidget().tabKeys.add(key); + + if (selected) { + getWidget().activeTabIndex = index; + } + getWidget().renderTab(tab, index, selected, hidden); + index++; + } + + int tabCount = getWidget().getTabCount(); + while (tabCount-- > index) { + getWidget().removeTab(index); + } + + for (int i = 0; i < getWidget().getTabCount(); i++) { + ComponentConnector p = getWidget().getTab(i); + // null for PlaceHolder widgets + if (p != null) { + oldWidgets.remove(p.getWidget()); + } + } + + // Detach any old tab widget, should be max 1 + for (Iterator<Widget> iterator = oldWidgets.iterator(); iterator + .hasNext();) { + Widget oldWidget = iterator.next(); + if (oldWidget.isAttached()) { + oldWidget.removeFromParent(); + } + } + + } + + @Override + public VTabsheetBase getWidget() { + return (VTabsheetBase) super.getWidget(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetConnector.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetConnector.java new file mode 100644 index 0000000000..7829934f70 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetConnector.java @@ -0,0 +1,105 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.layout.MayScrollChildren; +import com.vaadin.ui.TabSheet; + +@Connect(TabSheet.class) +public class TabsheetConnector extends TabsheetBaseConnector implements + SimpleManagedLayout, MayScrollChildren { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (isRealUpdate(uidl)) { + // Handle stylename changes before generics (might affect size + // calculations) + getWidget().handleStyleNames(uidl, getState()); + } + + super.updateFromUIDL(uidl, client); + if (!isRealUpdate(uidl)) { + return; + } + + // tabs; push or not + if (!isUndefinedWidth()) { + DOM.setStyleAttribute(getWidget().tabs, "overflow", "hidden"); + } else { + getWidget().showAllTabs(); + DOM.setStyleAttribute(getWidget().tabs, "width", ""); + DOM.setStyleAttribute(getWidget().tabs, "overflow", "visible"); + getWidget().updateDynamicWidth(); + } + + if (!isUndefinedHeight()) { + // Must update height after the styles have been set + getWidget().updateContentNodeHeight(); + getWidget().updateOpenTabSize(); + } + + getWidget().iLayout(); + + // Re run relative size update to ensure optimal scrollbars + // TODO isolate to situation that visible tab has undefined height + try { + client.handleComponentRelativeSize(getWidget().tp + .getWidget(getWidget().tp.getVisibleWidget())); + } catch (Exception e) { + // Ignore, most likely empty tabsheet + } + + getWidget().waitingForResponse = false; + } + + @Override + protected Widget createWidget() { + return GWT.create(VTabsheet.class); + } + + @Override + public VTabsheet getWidget() { + return (VTabsheet) super.getWidget(); + } + + public void updateCaption(ComponentConnector component) { + /* Tabsheet does not render its children's captions */ + } + + public void layout() { + VTabsheet tabsheet = getWidget(); + + tabsheet.updateContentNodeHeight(); + + if (isUndefinedWidth()) { + tabsheet.contentNode.getStyle().setProperty("width", ""); + } else { + int contentWidth = tabsheet.getOffsetWidth() + - tabsheet.getContentAreaBorderWidth(); + if (contentWidth < 0) { + contentWidth = 0; + } + tabsheet.contentNode.getStyle().setProperty("width", + contentWidth + "px"); + } + + tabsheet.updateOpenTabSize(); + if (isUndefinedWidth()) { + tabsheet.updateDynamicWidth(); + } + + tabsheet.iLayout(); + + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java index f1a5b31379..c97ede1252 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java @@ -2,14 +2,15 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.tabsheet; import java.util.Iterator; -import java.util.Set; +import java.util.List; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.dom.client.TableElement; import com.google.gwt.event.dom.client.BlurEvent; @@ -35,15 +36,16 @@ import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.FocusImpl; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.EventId; import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderInformation; -import com.vaadin.terminal.gwt.client.RenderSpace; import com.vaadin.terminal.gwt.client.TooltipInfo; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.label.VLabel; public class VTabsheet extends VTabsheetBase implements Focusable, FocusHandler, BlurHandler, KeyDownHandler { @@ -239,27 +241,33 @@ public class VTabsheet extends VTabsheetBase implements Focusable, private ApplicationConnection client; TabCaption(Tab tab, ApplicationConnection client) { - super(null, client); + super(client); this.client = client; this.tab = tab; } - @Override public boolean updateCaption(UIDL uidl) { - if (uidl.hasAttribute(ATTRIBUTE_DESCRIPTION) - || uidl.hasAttribute(ATTRIBUTE_ERROR)) { + if (uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION)) { TooltipInfo tooltipInfo = new TooltipInfo(); - tooltipInfo.setTitle(uidl - .getStringAttribute(ATTRIBUTE_DESCRIPTION)); - if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { - tooltipInfo.setErrorUidl(uidl.getErrors()); - } + tooltipInfo + .setTitle(uidl + .getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION)); + tooltipInfo + .setErrorMessage(uidl + .getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE)); client.registerTooltip(getTabsheet(), getElement(), tooltipInfo); } else { client.registerTooltip(getTabsheet(), getElement(), null); } - boolean ret = super.updateCaption(uidl); + // TODO need to call this instead of super because the caption does + // not have an owner + boolean ret = updateCaptionWithoutOwner( + uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE), + uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ICON)); setClosable(uidl.hasAttribute("closable")); @@ -291,33 +299,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return tab; } - @Override - public void setWidth(String width) { - super.setWidth(width); - if (BrowserInfo.get().isIE7()) { - /* - * IE7 apparently has problems with calculating width for - * floated elements inside a DIV with padding. Set the width - * explicitly for the caption. - */ - fixTextWidth(); - } - } - - private void fixTextWidth() { - Element captionText = getTextElement(); - if (captionText == null) { - return; - } - - int captionWidth = Util.getRequiredWidth(captionText); - int scrollWidth = captionText.getScrollWidth(); - if (scrollWidth > captionWidth) { - captionWidth = scrollWidth; - } - captionText.getStyle().setPropertyPx("width", captionWidth); - } - public void setClosable(boolean closable) { this.closable = closable; if (closable && closeButton == null) { @@ -552,45 +533,32 @@ public class VTabsheet extends VTabsheetBase implements Focusable, // Can't use "style" as it's already in use public static final String TAB_STYLE_NAME = "tabstyle"; + final Element tabs; // tabbar and 'scroller' container + Tab focusedTab; + /** + * The tabindex property (position in the browser's focus cycle.) Named like + * this to avoid confusion with activeTabIndex. + */ + int tabulatorIndex = 0; + private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel(); - private final Element tabs; // tabbar and 'scroller' container private final Element scroller; // tab-scroller element private final Element scrollerNext; // tab-scroller next button element private final Element scrollerPrev; // tab-scroller prev button element - private Tab focusedTab; - - /** - * The tabindex property (position in the browser's focus cycle.) Named like - * this to avoid confusion with activeTabIndex. - */ - private int tabulatorIndex = 0; - /** * The index of the first visible tab (when scrolled) */ private int scrollerIndex = 0; - private final TabBar tb = new TabBar(this); - private final VTabsheetPanel tp = new VTabsheetPanel(); - private final Element contentNode, deco; - - private String height; - private String width; + final TabBar tb = new TabBar(this); + final VTabsheetPanel tp = new VTabsheetPanel(); + final Element contentNode; - private boolean waitingForResponse; + private final Element deco; - private final RenderInformation renderInformation = new RenderInformation(); - - /** - * Previous visible widget is set invisible with CSS (not display: none, but - * visibility: hidden), to avoid flickering during render process. Normal - * visibility must be returned later when new widget is rendered. - */ - private Widget previousVisibleWidget; - - private boolean rendering = false; + boolean waitingForResponse; private String currentStyle; @@ -615,18 +583,14 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } addStyleDependentName("loading"); - // run updating variables in deferred command to bypass some FF - // optimization issues - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - previousVisibleWidget = tp.getWidget(tp.getVisibleWidget()); - DOM.setStyleAttribute( - DOM.getParent(previousVisibleWidget.getElement()), - "visibility", "hidden"); - client.updateVariable(id, "selected", tabKeys.get(tabIndex) - .toString(), true); - } - }); + // Hide the current contents so a loading indicator can be shown + // instead + Widget currentlyDisplayedWidget = tp.getWidget(tp + .getVisibleWidget()); + currentlyDisplayedWidget.getElement().getParentElement().getStyle() + .setVisibility(Visibility.HIDDEN); + client.updateVariable(id, "selected", tabKeys.get(tabIndex) + .toString(), true); waitingForResponse = true; } // Note that we return true when tabIndex == activeTabIndex; the active @@ -651,12 +615,16 @@ public class VTabsheet extends VTabsheetBase implements Focusable, client.updateVariable(id, "close", tabKeys.get(tabIndex), true); } - private boolean isDynamicWidth() { - return width == null || width.equals(""); + boolean isDynamicWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedWidth(); } - private boolean isDynamicHeight() { - return height == null || height.equals(""); + boolean isDynamicHeight() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedHeight(); } public VTabsheet() { @@ -742,85 +710,24 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return scrollerIndex > index; } - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - rendering = true; - - if (!uidl.getBooleanAttribute("cached")) { - // Handle stylename changes before generics (might affect size - // calculations) - handleStyleNames(uidl); - } - - super.updateFromUIDL(uidl, client); - if (cachedUpdate) { - rendering = false; - return; - } - - // tabs; push or not - if (!isDynamicWidth()) { - DOM.setStyleAttribute(tabs, "overflow", "hidden"); - } else { - showAllTabs(); - DOM.setStyleAttribute(tabs, "width", ""); - DOM.setStyleAttribute(tabs, "overflow", "visible"); - updateDynamicWidth(); - } - - if (!isDynamicHeight()) { - // Must update height after the styles have been set - updateContentNodeHeight(); - updateOpenTabSize(); - } - - if (uidl.hasAttribute("tabindex")) { - tabulatorIndex = uidl.getIntAttribute("tabindex"); - if (tabulatorIndex == -1) { - blur(); - } - } - - // If a tab was focused before, focus the new active tab - if (focusedTab != null && tb.getTabCount() > 0 && tabulatorIndex != -1) { - focus(); - } - - iLayout(); - // Re run relative size update to ensure optimal scrollbars - // TODO isolate to situation that visible tab has undefined height - try { - client.handleComponentRelativeSize(tp.getWidget(tp - .getVisibleWidget())); - } catch (Exception e) { - // Ignore, most likely empty tabsheet - } - - renderInformation.updateSize(getElement()); - - waitingForResponse = false; - rendering = false; - } - - private void handleStyleNames(UIDL uidl) { + void handleStyleNames(UIDL uidl, ComponentState state) { // Add proper stylenames for all elements (easier to prevent unwanted // style inheritance) - if (uidl.hasAttribute("style")) { - final String style = uidl.getStringAttribute("style"); - if (currentStyle != style) { - currentStyle = style; - final String[] styles = style.split(" "); + if (state.hasStyles()) { + final List<String> styles = state.getStyles(); + if (!currentStyle.equals(styles.toString())) { + currentStyle = styles.toString(); final String tabsBaseClass = TABS_CLASSNAME; String tabsClass = tabsBaseClass; final String contentBaseClass = CLASSNAME + "-content"; String contentClass = contentBaseClass; final String decoBaseClass = CLASSNAME + "-deco"; String decoClass = decoBaseClass; - for (int i = 0; i < styles.length; i++) { - tb.addStyleDependentName(styles[i]); - tabsClass += " " + tabsBaseClass + "-" + styles[i]; - contentClass += " " + contentBaseClass + "-" + styles[i]; - decoClass += " " + decoBaseClass + "-" + styles[i]; + for (String style : styles) { + tb.addStyleDependentName(style); + tabsClass += " " + tabsBaseClass + "-" + style; + contentClass += " " + contentBaseClass + "-" + style; + decoClass += " " + decoBaseClass + "-" + style; } DOM.setElementProperty(tabs, "className", tabsClass); DOM.setElementProperty(contentNode, "className", contentClass); @@ -844,7 +751,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } } - private void updateDynamicWidth() { + void updateDynamicWidth() { // Find width consumed by tabs TableCellElement spacerCell = ((TableElement) tb.getElement().cast()) .getRows().getItem(0).getCells().getItem(tb.getTabCount()); @@ -921,22 +828,24 @@ public class VTabsheet extends VTabsheetBase implements Focusable, tab.recalculateCaptionWidth(); UIDL tabContentUIDL = null; - Paintable tabContent = null; + ComponentConnector tabContentPaintable = null; + Widget tabContentWidget = null; if (tabUidl.getChildCount() > 0) { tabContentUIDL = tabUidl.getChildUIDL(0); - tabContent = client.getPaintable(tabContentUIDL); + tabContentPaintable = client.getPaintable(tabContentUIDL); + tabContentWidget = tabContentPaintable.getWidget(); } - if (tabContent != null) { + if (tabContentPaintable != null) { /* This is a tab with content information */ - int oldIndex = tp.getWidgetIndex((Widget) tabContent); + int oldIndex = tp.getWidgetIndex(tabContentWidget); if (oldIndex != -1 && oldIndex != index) { /* * The tab has previously been rendered in another position so * we must move the cached content to correct position */ - tp.insert((Widget) tabContent, index); + tp.insert(tabContentWidget, index); } } else { /* A tab whose content has not yet been loaded */ @@ -960,10 +869,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } else { if (tabContentUIDL != null) { // updating a drawn child on hidden tab - if (tp.getWidgetIndex((Widget) tabContent) < 0) { - tp.insert((Widget) tabContent, index); + if (tp.getWidgetIndex(tabContentWidget) < 0) { + tp.insert(tabContentWidget, index); } - tabContent.updateFromUIDL(tabContentUIDL, client); } else if (tp.getWidgetCount() <= index) { tp.add(new PlaceHolder()); } @@ -986,57 +894,39 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } private void renderContent(final UIDL contentUIDL) { - final Paintable content = client.getPaintable(contentUIDL); + final ComponentConnector content = client.getPaintable(contentUIDL); + Widget newWidget = content.getWidget(); if (tp.getWidgetCount() > activeTabIndex) { Widget old = tp.getWidget(activeTabIndex); - if (old != content) { + if (old != newWidget) { tp.remove(activeTabIndex); - if (old instanceof Paintable) { - client.unregisterPaintable((Paintable) old); + ConnectorMap paintableMap = ConnectorMap.get(client); + if (paintableMap.isConnector(old)) { + paintableMap.unregisterConnector(paintableMap + .getConnector(old)); } - tp.insert((Widget) content, activeTabIndex); + tp.insert(content.getWidget(), activeTabIndex); } } else { - tp.add((Widget) content); + tp.add(content.getWidget()); } tp.showWidget(activeTabIndex); VTabsheet.this.iLayout(); - (content).updateFromUIDL(contentUIDL, client); /* * The size of a cached, relative sized component must be updated to * report correct size to updateOpenTabSize(). */ if (contentUIDL.getBooleanAttribute("cached")) { - client.handleComponentRelativeSize((Widget) content); + client.handleComponentRelativeSize(content.getWidget()); } updateOpenTabSize(); VTabsheet.this.removeStyleDependentName("loading"); - if (previousVisibleWidget != null) { - DOM.setStyleAttribute( - DOM.getParent(previousVisibleWidget.getElement()), - "visibility", ""); - previousVisibleWidget = null; - } - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - this.height = height; - updateContentNodeHeight(); - - if (!rendering) { - updateOpenTabSize(); - iLayout(); - // TODO Check if this is needed - client.runDescendentsLayout(this); - } } - private void updateContentNodeHeight() { - if (height != null && !"".equals(height)) { + void updateContentNodeHeight() { + if (!isDynamicHeight()) { int contentHeight = getOffsetHeight(); contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight"); contentHeight -= tb.getOffsetHeight(); @@ -1046,55 +936,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, // Set proper values for content element DOM.setStyleAttribute(contentNode, "height", contentHeight + "px"); - renderSpace.setHeight(contentHeight); } else { DOM.setStyleAttribute(contentNode, "height", ""); - renderSpace.setHeight(0); - } - } - - @Override - public void setWidth(String width) { - if ((this.width == null && width.equals("")) - || (this.width != null && this.width.equals(width))) { - return; - } - - super.setWidth(width); - if (width.equals("")) { - width = null; } - this.width = width; - if (width == null) { - renderSpace.setWidth(0); - contentNode.getStyle().setProperty("width", ""); - } else { - int contentWidth = getOffsetWidth() - getContentAreaBorderWidth(); - if (contentWidth < 0) { - contentWidth = 0; - } - contentNode.getStyle().setProperty("width", contentWidth + "px"); - renderSpace.setWidth(contentWidth); - } - - if (!rendering) { - if (isDynamicHeight()) { - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, tp, - this); - } - - updateOpenTabSize(); - iLayout(); - // TODO Check if this is needed - client.runDescendentsLayout(this); - - } - } public void iLayout() { updateTabScroller(); - tp.runWebkitOverflowAutoFix(); } /** @@ -1102,7 +950,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, * position: absolute (to work around a firefox flickering bug) we must keep * this up-to-date by hand. */ - private void updateOpenTabSize() { + void updateOpenTabSize() { /* * The overflow=auto element must have a height specified, otherwise it * will be just as high as the contents and no scrollbars will appear @@ -1112,10 +960,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, int minWidth = 0; if (!isDynamicHeight()) { - height = renderSpace.getHeight(); + height = contentNode.getOffsetHeight(); } if (!isDynamicWidth()) { - width = renderSpace.getWidth(); + width = contentNode.getOffsetWidth() - getContentAreaBorderWidth(); } else { /* * If the tabbar is wider than the content we need to use the tabbar @@ -1132,8 +980,11 @@ public class VTabsheet extends VTabsheetBase implements Focusable, * Layouts the tab-scroller elements, and applies styles. */ private void updateTabScroller() { - if (width != null) { - DOM.setStyleAttribute(tabs, "width", width); + if (!isDynamicWidth()) { + ComponentConnector paintable = ConnectorMap.get(client) + .getConnector(this); + DOM.setStyleAttribute(tabs, "width", paintable.getState() + .getWidth()); } // Make sure scrollerIndex is valid @@ -1178,7 +1029,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } - private void showAllTabs() { + void showAllTabs() { scrollerIndex = tb.getFirstVisibleTab(); for (int i = 0; i < tb.getTabCount(); i++) { Tab t = tb.getTab(i); @@ -1215,92 +1066,29 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } @Override - protected Iterator getPaintableIterator() { + protected Iterator<Widget> getWidgetIterator() { return tp.iterator(); } - public boolean hasChildComponent(Widget component) { - if (tp.getWidgetIndex(component) < 0) { - return false; - } else { - return true; - } - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - tp.replaceComponent(oldComponent, newComponent); - } - - public void updateCaption(Paintable component, UIDL uidl) { - /* Tabsheet does not render its children's captions */ - } - - public boolean requestLayout(Set<Paintable> child) { - if (!isDynamicHeight() && !isDynamicWidth()) { - /* - * If the height and width has been specified for this container the - * child components cannot make the size of the layout change - */ - // layout size change may affect its available space (scrollbars) - for (Paintable paintable : child) { - client.handleComponentRelativeSize((Widget) paintable); - } - return true; - } - - updateOpenTabSize(); - - if (renderInformation.updateSize(getElement())) { - /* - * Size has changed so we let the child components know about the - * new size. - */ - iLayout(); - client.runDescendentsLayout(this); - /* - * Firefox and IE9 need a nudge to prevent unwanted scrollbars with - * Chameleon theme (#8625) - */ - if (BrowserInfo.get().isFirefox() || BrowserInfo.get().isIE9()) { - Util.setStyleTemporarily((Element) tp.getElement() - .getFirstChildElement(), "overflow", ""); - } - return false; - } else { - /* - * Size has not changed so we do not need to propagate the event - * further - */ - return true; - } - - } - private int borderW = -1; - private int getContentAreaBorderWidth() { + int getContentAreaBorderWidth() { if (borderW < 0) { borderW = Util.measureHorizontalBorder(contentNode); } return borderW; } - private final RenderSpace renderSpace = new RenderSpace(0, 0, true); - - public RenderSpace getAllocatedSpace(Widget child) { - // All tabs have equal amount of space allocated - return renderSpace; - } - @Override protected int getTabCount() { return tb.getTabCount(); } @Override - protected Paintable getTab(int index) { + protected ComponentConnector getTab(int index) { if (tp.getWidgetCount() > index) { - return (Paintable) tp.getWidget(index); + Widget widget = tp.getWidget(index); + return ConnectorMap.get(client).getConnector(widget); } return null; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetBase.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetBase.java new file mode 100644 index 0000000000..ed9883dd35 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetBase.java @@ -0,0 +1,75 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.UIDL; + +public abstract class VTabsheetBase extends ComplexPanel { + + protected String id; + protected ApplicationConnection client; + + protected final ArrayList<String> tabKeys = new ArrayList<String>(); + protected int activeTabIndex = 0; + protected boolean disabled; + protected boolean readonly; + protected Set<String> disabledTabKeys = new HashSet<String>(); + + public VTabsheetBase(String classname) { + setElement(DOM.createDiv()); + setStyleName(classname); + } + + /** + * @return a list of currently shown Widgets + */ + abstract protected Iterator<Widget> getWidgetIterator(); + + /** + * Clears current tabs and contents + */ + abstract protected void clearPaintables(); + + /** + * Implement in extending classes. This method should render needed elements + * and set the visibility of the tab according to the 'selected' parameter. + */ + protected abstract void renderTab(final UIDL tabUidl, int index, + boolean selected, boolean hidden); + + /** + * Implement in extending classes. This method should render any previously + * non-cached content and set the activeTabIndex property to the specified + * index. + */ + protected abstract void selectTab(int index, final UIDL contentUidl); + + /** + * Implement in extending classes. This method should return the number of + * tabs currently rendered. + */ + protected abstract int getTabCount(); + + /** + * Implement in extending classes. This method should return the Paintable + * corresponding to the given index. + */ + protected abstract ComponentConnector getTab(int index); + + /** + * Implement in extending classes. This method should remove the rendered + * tab with the specified index. + */ + protected abstract void removeTab(int index); +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPanel.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java index 126b0ebea1..f2b37c3a1c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.tabsheet; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; @@ -13,12 +13,12 @@ import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; /** * A panel that displays all of its child widgets in a 'deck', where only one * can be visible at a time. It is used by - * {@link com.vaadin.terminal.gwt.client.ui.VTabsheet}. + * {@link com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheet}. * * This class has the same basic functionality as the GWT DeckPanel * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it @@ -141,8 +141,11 @@ public class VTabsheetPanel extends ComplexPanel { hide(DOM.getParent(visibleWidget.getElement())); } visibleWidget = newVisible; - unHide(DOM.getParent(visibleWidget.getElement())); } + // Always ensure the selected tab is visible. If server prevents a tab + // change we might end up here with visibleWidget == newVisible but its + // parent is still hidden. + unHide(DOM.getParent(visibleWidget.getElement())); } private void hide(Element e) { @@ -190,16 +193,12 @@ public class VTabsheetPanel extends ComplexPanel { getElement().getStyle().setPropertyPx("height", height); // widget wrapper height - wrapperDiv.getStyle().setPropertyPx("height", height); - runWebkitOverflowAutoFix(); - } - - public void runWebkitOverflowAutoFix() { - if (visibleWidget != null) { - Util.runWebkitOverflowAutoFix(DOM.getParent(visibleWidget - .getElement())); + if (dynamicHeight) { + wrapperDiv.getStyle().clearHeight(); + } else { + // widget wrapper height + wrapperDiv.getStyle().setPropertyPx("height", height); } - } public void replaceComponent(Widget oldComponent, Widget newComponent) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/textarea/TextAreaConnector.java b/src/com/vaadin/terminal/gwt/client/ui/textarea/TextAreaConnector.java new file mode 100644 index 0000000000..0f3ae0ad4f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/textarea/TextAreaConnector.java @@ -0,0 +1,42 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.textarea; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.textfield.TextFieldConnector; +import com.vaadin.ui.TextArea; + +@Connect(TextArea.class) +public class TextAreaConnector extends TextFieldConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Call parent renderer explicitly + super.updateFromUIDL(uidl, client); + + if (uidl.hasAttribute("rows")) { + getWidget().setRows(uidl.getIntAttribute("rows")); + } + + if (getWidget().getMaxLength() >= 0) { + getWidget().sinkEvents(Event.ONKEYUP); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextArea.class); + } + + @Override + public VTextArea getWidget() { + return (VTextArea) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java b/src/com/vaadin/terminal/gwt/client/ui/textarea/VTextArea.java index c6107e3b0e..c600b2fd1e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java +++ b/src/com/vaadin/terminal/gwt/client/ui/textarea/VTextArea.java @@ -2,15 +2,14 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.textarea; import com.google.gwt.core.client.Scheduler; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; /** * This class represents a multiline textfield (textarea). @@ -29,20 +28,6 @@ public class VTextArea extends VTextField { setStyleName(CLASSNAME); } - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Call parent renderer explicitly - super.updateFromUIDL(uidl, client); - - if (uidl.hasAttribute("rows")) { - setRows(uidl.getIntAttribute("rows")); - } - - if (getMaxLength() >= 0) { - sinkEvents(Event.ONKEYUP); - } - } - public void setRows(int rows) { setRows(getElement(), rows); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/textfield/TextFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/textfield/TextFieldConnector.java new file mode 100644 index 0000000000..7e9e786676 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/textfield/TextFieldConnector.java @@ -0,0 +1,123 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.textfield; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.ui.TextField; + +@Connect(value = TextField.class, loadStyle = LoadStyle.EAGER) +public class TextFieldConnector extends AbstractFieldConnector implements + Paintable, BeforeShortcutActionListener { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().setReadOnly(isReadOnly()); + + getWidget().inputPrompt = uidl + .getStringAttribute(VTextField.ATTR_INPUTPROMPT); + + getWidget().setMaxLength( + uidl.hasAttribute("maxLength") ? uidl + .getIntAttribute("maxLength") : -1); + + getWidget().immediate = getState().isImmediate(); + + getWidget().listenTextChangeEvents = hasEventListener("ie"); + if (getWidget().listenTextChangeEvents) { + getWidget().textChangeEventMode = uidl + .getStringAttribute(VTextField.ATTR_TEXTCHANGE_EVENTMODE); + if (getWidget().textChangeEventMode + .equals(VTextField.TEXTCHANGE_MODE_EAGER)) { + getWidget().textChangeEventTimeout = 1; + } else { + getWidget().textChangeEventTimeout = uidl + .getIntAttribute(VTextField.ATTR_TEXTCHANGE_TIMEOUT); + if (getWidget().textChangeEventTimeout < 1) { + // Sanitize and allow lazy/timeout with timeout set to 0 to + // work as eager + getWidget().textChangeEventTimeout = 1; + } + } + getWidget().sinkEvents(VTextField.TEXTCHANGE_EVENTS); + getWidget().attachCutEventListener(getWidget().getElement()); + } + + if (uidl.hasAttribute("cols")) { + getWidget().setColumns( + new Integer(uidl.getStringAttribute("cols")).intValue()); + } + + final String text = uidl.getStringVariable("text"); + + /* + * We skip the text content update if field has been repainted, but text + * has not been changed. Additional sanity check verifies there is no + * change in the que (in which case we count more on the server side + * value). + */ + if (!(uidl + .getBooleanAttribute(VTextField.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS) + && getWidget().valueBeforeEdit != null && text + .equals(getWidget().valueBeforeEdit))) { + getWidget().updateFieldContent(text); + } + + if (uidl.hasAttribute("selpos")) { + final int pos = uidl.getIntAttribute("selpos"); + final int length = uidl.getIntAttribute("sellen"); + /* + * Gecko defers setting the text so we need to defer the selection. + */ + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + getWidget().setSelectionRange(pos, length); + } + }); + } + + // Here for backward compatibility; to be moved to TextArea. + // Optimization: server does not send attribute for the default 'true' + // state. + if (uidl.hasAttribute("wordwrap") + && uidl.getBooleanAttribute("wordwrap") == false) { + getWidget().setWordwrap(false); + } else { + getWidget().setWordwrap(true); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextField.class); + } + + @Override + public VTextField getWidget() { + return (VTextField) super.getWidget(); + } + + public void onBeforeShortcutAction(Event e) { + getWidget().valueChange(false); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java index d1e4f7ca5b..7bd392b503 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java @@ -2,9 +2,8 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.textfield; -import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; @@ -15,7 +14,6 @@ import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; @@ -24,11 +22,9 @@ import com.google.gwt.user.client.ui.TextBoxBase; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.terminal.gwt.client.ui.Field; /** * This class represents a basic text input field with one row. @@ -36,9 +32,8 @@ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutAct * @author Vaadin Ltd. * */ -public class VTextField extends TextBoxBase implements Paintable, Field, - ChangeHandler, FocusHandler, BlurHandler, BeforeShortcutActionListener, - KeyDownHandler { +public class VTextField extends TextBoxBase implements Field, ChangeHandler, + FocusHandler, BlurHandler, KeyDownHandler { public static final String VAR_CUR_TEXT = "curText"; @@ -52,11 +47,11 @@ public class VTextField extends TextBoxBase implements Paintable, Field, */ public static final String CLASSNAME_FOCUS = "focus"; - protected String id; + protected String paintableId; protected ApplicationConnection client; - private String valueBeforeEdit = null; + protected String valueBeforeEdit = null; /** * Set to false if a text change event has been sent since the last value @@ -65,20 +60,18 @@ public class VTextField extends TextBoxBase implements Paintable, Field, */ private boolean valueBeforeEditIsSynced = true; - private boolean immediate = false; - private int extraHorizontalPixels = -1; - private int extraVerticalPixels = -1; + protected boolean immediate = false; private int maxLength = -1; private static final String CLASSNAME_PROMPT = "prompt"; - private static final String ATTR_INPUTPROMPT = "prompt"; + protected static final String ATTR_INPUTPROMPT = "prompt"; public static final String ATTR_TEXTCHANGE_TIMEOUT = "iet"; public static final String VAR_CURSOR = "c"; public static final String ATTR_TEXTCHANGE_EVENTMODE = "iem"; - private static final String TEXTCHANGE_MODE_EAGER = "EAGER"; + protected static final String TEXTCHANGE_MODE_EAGER = "EAGER"; private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT"; - private String inputPrompt = null; + protected String inputPrompt = null; private boolean prompting = false; private int lastCursorPos = -1; private boolean wordwrap = true; @@ -89,12 +82,6 @@ public class VTextField extends TextBoxBase implements Paintable, Field, protected VTextField(Element node) { super(node); - if (BrowserInfo.get().getIEVersion() > 0 - && BrowserInfo.get().getIEVersion() < 8) { - // Fixes IE margin problem (#2058) - DOM.setStyleAttribute(node, "marginTop", "-1px"); - DOM.setStyleAttribute(node, "marginBottom", "-1px"); - } setStyleName(CLASSNAME); addChangeHandler(this); if (BrowserInfo.get().isIE()) { @@ -117,7 +104,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, * Eager polling for a change is bit dum and heavy operation, so I guess we * should first try to survive without. */ - private static final int TEXTCHANGE_EVENTS = Event.ONPASTE + protected static final int TEXTCHANGE_EVENTS = Event.ONPASTE | Event.KEYEVENTS | Event.ONMOUSEUP; @Override @@ -163,7 +150,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, client.sendPendingVariableChanges(); } else { // Default case - just send an immediate text change message - client.updateVariable(id, VAR_CUR_TEXT, text, true); + client.updateVariable(paintableId, VAR_CUR_TEXT, text, true); // Shouldn't investigate valueBeforeEdit to avoid duplicate text // change events as the states are not in sync any more @@ -185,9 +172,9 @@ public class VTextField extends TextBoxBase implements Paintable, Field, } }; private boolean scheduled = false; - private boolean listenTextChangeEvents; - private String textChangeEventMode; - private int textChangeEventTimeout; + protected boolean listenTextChangeEvents; + protected String textChangeEventMode; + protected int textChangeEventTimeout; private void deferTextChangeEvent() { if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) { @@ -220,129 +207,19 @@ public class VTextField extends TextBoxBase implements Paintable, Field, super.setReadOnly(readOnly); } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - id = uidl.getId(); - - if (client.updateComponent(this, uidl, true)) { - return; - } - - if (uidl.getBooleanAttribute("readonly")) { - setReadOnly(true); - } else { - setReadOnly(false); - } - - inputPrompt = uidl.getStringAttribute(ATTR_INPUTPROMPT); - - setMaxLength(uidl.hasAttribute("maxLength") ? uidl - .getIntAttribute("maxLength") : -1); - - immediate = uidl.getBooleanAttribute("immediate"); - - listenTextChangeEvents = client.hasEventListeners(this, "ie"); - if (listenTextChangeEvents) { - textChangeEventMode = uidl - .getStringAttribute(ATTR_TEXTCHANGE_EVENTMODE); - if (textChangeEventMode.equals(TEXTCHANGE_MODE_EAGER)) { - textChangeEventTimeout = 1; - } else { - textChangeEventTimeout = uidl - .getIntAttribute(ATTR_TEXTCHANGE_TIMEOUT); - if (textChangeEventTimeout < 1) { - // Sanitize and allow lazy/timeout with timeout set to 0 to - // work as eager - textChangeEventTimeout = 1; - } - } - sinkEvents(TEXTCHANGE_EVENTS); - attachCutEventListener(getElement()); - } - - if (uidl.hasAttribute("cols")) { - setColumns(new Integer(uidl.getStringAttribute("cols")).intValue()); - } - - final String text = uidl.getStringVariable("text"); - - /* - * We skip the text content update if field has been repainted, but text - * has not been changed. Additional sanity check verifies there is no - * change in the que (in which case we count more on the server side - * value). - */ - if (!(uidl.getBooleanAttribute(ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS) - && valueBeforeEdit != null && text.equals(valueBeforeEdit))) { - updateFieldContent(text); - } - - if (uidl.hasAttribute("selpos")) { - final int pos = uidl.getIntAttribute("selpos"); - final int length = uidl.getIntAttribute("sellen"); - /* - * Gecko defers setting the text so we need to defer the selection. - */ - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - setSelectionRange(pos, length); - } - }); - } - - // Here for backward compatibility; to be moved to TextArea. - // Optimization: server does not send attribute for the default 'true' - // state. - if (uidl.hasAttribute("wordwrap") - && uidl.getBooleanAttribute("wordwrap") == false) { - setWordwrap(false); - } else { - setWordwrap(true); - } - } - - private void updateFieldContent(final String text) { + protected void updateFieldContent(final String text) { setPrompting(inputPrompt != null && focusedTextField != this && (text.equals(""))); - if (BrowserInfo.get().isFF3()) { - /* - * Firefox 3 is really sluggish when updating input attached to dom. - * Some optimizations seems to work much better in Firefox3 if we - * update the actual content lazily when the rest of the DOM has - * stabilized. In tests, about ten times better performance is - * achieved with this optimization. See for eg. #2898 - */ - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - String fieldValue; - if (prompting) { - fieldValue = isReadOnly() ? "" : inputPrompt; - addStyleDependentName(CLASSNAME_PROMPT); - } else { - fieldValue = text; - removeStyleDependentName(CLASSNAME_PROMPT); - } - /* - * Avoid resetting the old value. Prevents cursor flickering - * which then again happens due to this Gecko hack. - */ - if (!getText().equals(fieldValue)) { - setText(fieldValue); - } - } - }); + String fieldValue; + if (prompting) { + fieldValue = isReadOnly() ? "" : inputPrompt; + addStyleDependentName(CLASSNAME_PROMPT); } else { - String fieldValue; - if (prompting) { - fieldValue = isReadOnly() ? "" : inputPrompt; - addStyleDependentName(CLASSNAME_PROMPT); - } else { - fieldValue = text; - removeStyleDependentName(CLASSNAME_PROMPT); - } - setText(fieldValue); + fieldValue = text; + removeStyleDependentName(CLASSNAME_PROMPT); } + setText(fieldValue); lastTextChangeString = valueBeforeEdit = text; valueBeforeEditIsSynced = true; @@ -358,7 +235,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, /*-{ var me = this; el.oncut = $entry(function() { - me.@com.vaadin.terminal.gwt.client.ui.VTextField::onCut()(); + me.@com.vaadin.terminal.gwt.client.ui.textfield.VTextField::onCut()(); }); }-*/; @@ -384,7 +261,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, } } - private void setMaxLength(int newMaxLength) { + protected void setMaxLength(int newMaxLength) { if (newMaxLength >= 0) { maxLength = newMaxLength; if (getElement().getTagName().toLowerCase().equals("textarea")) { @@ -403,7 +280,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, } - protected int getMaxLength() { + public int getMaxLength() { return maxLength; } @@ -420,20 +297,20 @@ public class VTextField extends TextBoxBase implements Paintable, Field, * true if the field was blurred */ public void valueChange(boolean blurred) { - if (client != null && id != null) { + if (client != null && paintableId != null) { boolean sendBlurEvent = false; boolean sendValueChange = false; if (blurred && client.hasEventListeners(this, EventId.BLUR)) { sendBlurEvent = true; - client.updateVariable(id, EventId.BLUR, "", false); + client.updateVariable(paintableId, EventId.BLUR, "", false); } String newText = getText(); if (!prompting && newText != null && !newText.equals(valueBeforeEdit)) { sendValueChange = immediate; - client.updateVariable(id, "text", getText(), false); + client.updateVariable(paintableId, "text", getText(), false); valueBeforeEdit = newText; valueBeforeEditIsSynced = true; } @@ -466,7 +343,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, if (Util.isAttachedAndDisplayed(this)) { int cursorPos = getCursorPos(); if (lastCursorPos != cursorPos) { - client.updateVariable(id, VAR_CURSOR, cursorPos, false); + client.updateVariable(paintableId, VAR_CURSOR, cursorPos, false); lastCursorPos = cursorPos; return true; } @@ -488,14 +365,10 @@ public class VTextField extends TextBoxBase implements Paintable, Field, setText(""); removeStyleDependentName(CLASSNAME_PROMPT); setPrompting(false); - if (BrowserInfo.get().isIE6()) { - // IE6 does not show the cursor when tabbing into the field - setCursorPos(0); - } } focusedTextField = this; if (client.hasEventListeners(this, EventId.FOCUS)) { - client.updateVariable(client.getPid(this), EventId.FOCUS, "", true); + client.updateVariable(paintableId, EventId.FOCUS, "", true); } } @@ -537,79 +410,6 @@ public class VTextField extends TextBoxBase implements Paintable, Field, } catch (e) {} }-*/; - /** - * @return space used by components paddings and borders - */ - private int getExtraHorizontalPixels() { - if (extraHorizontalPixels < 0) { - detectExtraSizes(); - } - return extraHorizontalPixels; - } - - /** - * @return space used by components paddings and borders - */ - private int getExtraVerticalPixels() { - if (extraVerticalPixels < 0) { - detectExtraSizes(); - } - return extraVerticalPixels; - } - - /** - * Detects space used by components paddings and borders. Used when - * relational size are used. - */ - private void detectExtraSizes() { - Element clone = Util.cloneNode(getElement(), false); - DOM.setElementAttribute(clone, "id", ""); - DOM.setStyleAttribute(clone, "visibility", "hidden"); - DOM.setStyleAttribute(clone, "position", "absolute"); - // due FF3 bug set size to 10px and later subtract it from extra pixels - DOM.setStyleAttribute(clone, "width", "10px"); - DOM.setStyleAttribute(clone, "height", "10px"); - DOM.appendChild(DOM.getParent(getElement()), clone); - extraHorizontalPixels = DOM.getElementPropertyInt(clone, "offsetWidth") - 10; - extraVerticalPixels = DOM.getElementPropertyInt(clone, "offsetHeight") - 10; - - DOM.removeChild(DOM.getParent(getElement()), clone); - } - - @Override - public void setHeight(String height) { - if (height.endsWith("px")) { - int h = Integer.parseInt(height.substring(0, height.length() - 2)); - h -= getExtraVerticalPixels(); - if (h < 0) { - h = 0; - } - - super.setHeight(h + "px"); - } else { - super.setHeight(height); - } - } - - @Override - public void setWidth(String width) { - if (width.endsWith("px")) { - int w = Integer.parseInt(width.substring(0, width.length() - 2)); - w -= getExtraHorizontalPixels(); - if (w < 0) { - w = 0; - } - - super.setWidth(w + "px"); - } else { - super.setWidth(width); - } - } - - public void onBeforeShortcutAction(Event e) { - valueChange(false); - } - // Here for backward compatibility; to be moved to TextArea public void setWordwrap(boolean enabled) { if (enabled == wordwrap) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/tree/TreeConnector.java b/src/com/vaadin/terminal/gwt/client/ui/tree/TreeConnector.java new file mode 100644 index 0000000000..ebe5d22e99 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tree/TreeConnector.java @@ -0,0 +1,260 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tree; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.TooltipInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.tree.VTree.TreeNode; +import com.vaadin.ui.Tree; + +@Connect(Tree.class) +public class TreeConnector extends AbstractComponentConnector implements + Paintable { + + public static final String ATTRIBUTE_NODE_STYLE = "style"; + public static final String ATTRIBUTE_NODE_CAPTION = "caption"; + public static final String ATTRIBUTE_NODE_ICON = "icon"; + + public static final String ATTRIBUTE_ACTION_CAPTION = "caption"; + public static final String ATTRIBUTE_ACTION_ICON = ATTRIBUTE_NODE_ICON; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().rendering = true; + + getWidget().client = client; + + if (uidl.hasAttribute("partialUpdate")) { + handleUpdate(uidl); + getWidget().rendering = false; + return; + } + + getWidget().paintableId = uidl.getId(); + + getWidget().immediate = getState().isImmediate(); + + getWidget().disabled = !isEnabled(); + getWidget().readonly = isReadOnly(); + + getWidget().dragMode = uidl.hasAttribute("dragMode") ? uidl + .getIntAttribute("dragMode") : 0; + + getWidget().isNullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + + if (uidl.hasAttribute("alb")) { + getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); + } + + getWidget().body.clear(); + // clear out any references to nodes that no longer are attached + getWidget().clearNodeToKeyMap(); + TreeNode childTree = null; + UIDL childUidl = null; + for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) { + childUidl = (UIDL) i.next(); + if ("actions".equals(childUidl.getTag())) { + updateActionMap(childUidl); + continue; + } else if ("-ac".equals(childUidl.getTag())) { + getWidget().updateDropHandler(childUidl); + continue; + } + childTree = getWidget().new TreeNode(); + updateNodeFromUIDL(childTree, childUidl); + getWidget().body.add(childTree); + childTree.addStyleDependentName("root"); + childTree.childNodeContainer.addStyleDependentName("root"); + } + if (childTree != null && childUidl != null) { + boolean leaf = !childUidl.getTag().equals("node"); + childTree.addStyleDependentName(leaf ? "leaf-last" : "last"); + childTree.childNodeContainer.addStyleDependentName("last"); + } + final String selectMode = uidl.getStringAttribute("selectmode"); + getWidget().selectable = !"none".equals(selectMode); + getWidget().isMultiselect = "multi".equals(selectMode); + + if (getWidget().isMultiselect) { + if (BrowserInfo.get().isTouchDevice()) { + // Always use the simple mode for touch devices that do not have + // shift/ctrl keys (#8595) + getWidget().multiSelectMode = VTree.MULTISELECT_MODE_SIMPLE; + } else { + getWidget().multiSelectMode = uidl + .getIntAttribute("multiselectmode"); + } + } + + getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected"); + + // Update lastSelection and focusedNode to point to *actual* nodes again + // after the old ones have been cleared from the body. This fixes focus + // and keyboard navigation issues as described in #7057 and other + // tickets. + if (getWidget().lastSelection != null) { + getWidget().lastSelection = getWidget().getNodeByKey( + getWidget().lastSelection.key); + } + if (getWidget().focusedNode != null) { + getWidget().setFocusedNode( + getWidget().getNodeByKey(getWidget().focusedNode.key)); + } + + if (getWidget().lastSelection == null + && getWidget().focusedNode == null + && !getWidget().selectedIds.isEmpty()) { + getWidget().setFocusedNode( + getWidget().getNodeByKey( + getWidget().selectedIds.iterator().next())); + getWidget().focusedNode.setFocused(false); + } + + getWidget().rendering = false; + + } + + @Override + protected Widget createWidget() { + return GWT.create(VTree.class); + } + + @Override + public VTree getWidget() { + return (VTree) super.getWidget(); + } + + private void handleUpdate(UIDL uidl) { + final TreeNode rootNode = getWidget().getNodeByKey( + uidl.getStringAttribute("rootKey")); + if (rootNode != null) { + if (!rootNode.getState()) { + // expanding node happened server side + rootNode.setState(true, false); + } + renderChildNodes(rootNode, (Iterator) uidl.getChildIterator()); + } + } + + /** + * Registers action for the root and also for individual nodes + * + * @param uidl + */ + private void updateActionMap(UIDL uidl) { + final Iterator<?> it = uidl.getChildIterator(); + while (it.hasNext()) { + final UIDL action = (UIDL) it.next(); + final String key = action.getStringAttribute("key"); + final String caption = action + .getStringAttribute(ATTRIBUTE_ACTION_CAPTION); + String iconUrl = null; + if (action.hasAttribute(ATTRIBUTE_ACTION_ICON)) { + iconUrl = getConnection().translateVaadinUri( + action.getStringAttribute(ATTRIBUTE_ACTION_ICON)); + } + getWidget().registerAction(key, caption, iconUrl); + } + + } + + public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) { + String nodeKey = uidl.getStringAttribute("key"); + treeNode.setText(uidl.getStringAttribute(ATTRIBUTE_NODE_CAPTION)); + treeNode.key = nodeKey; + + getWidget().registerNode(treeNode); + + if (uidl.hasAttribute("al")) { + treeNode.actionKeys = uidl.getStringArrayAttribute("al"); + } + + if (uidl.getTag().equals("node")) { + if (uidl.getChildCount() == 0) { + treeNode.childNodeContainer.setVisible(false); + } else { + renderChildNodes(treeNode, (Iterator) uidl.getChildIterator()); + treeNode.childrenLoaded = true; + } + } else { + treeNode.addStyleName(TreeNode.CLASSNAME + "-leaf"); + } + if (uidl.hasAttribute(ATTRIBUTE_NODE_STYLE)) { + treeNode.setNodeStyleName(uidl + .getStringAttribute(ATTRIBUTE_NODE_STYLE)); + } + + String description = uidl.getStringAttribute("descr"); + if (description != null && getConnection() != null) { + // Set tooltip + TooltipInfo info = new TooltipInfo(description); + getConnection().registerTooltip(this, nodeKey, info); + } else { + // Remove possible previous tooltip + getConnection().registerTooltip(this, nodeKey, null); + } + + if (uidl.getBooleanAttribute("expanded") && !treeNode.getState()) { + treeNode.setState(true, false); + } + + if (uidl.getBooleanAttribute("selected")) { + treeNode.setSelected(true); + // ensure that identifier is in selectedIds array (this may be a + // partial update) + getWidget().selectedIds.add(nodeKey); + } + + treeNode.setIcon(uidl.getStringAttribute(ATTRIBUTE_NODE_ICON)); + } + + void renderChildNodes(TreeNode containerNode, Iterator<UIDL> i) { + containerNode.childNodeContainer.clear(); + containerNode.childNodeContainer.setVisible(true); + while (i.hasNext()) { + final UIDL childUidl = i.next(); + // actions are in bit weird place, don't mix them with children, + // but current node's actions + if ("actions".equals(childUidl.getTag())) { + updateActionMap(childUidl); + continue; + } + final TreeNode childTree = getWidget().new TreeNode(); + updateNodeFromUIDL(childTree, childUidl); + containerNode.childNodeContainer.add(childTree); + if (!i.hasNext()) { + childTree + .addStyleDependentName(childTree.isLeaf() ? "leaf-last" + : "last"); + childTree.childNodeContainer.addStyleDependentName("last"); + } + } + containerNode.childrenLoaded = true; + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().isPropertyReadOnly(); + } + + @Override + public AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTree.java b/src/com/vaadin/terminal/gwt/client/ui/tree/VTree.java index 1d5ec206aa..6f19cba957 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTree.java +++ b/src/com/vaadin/terminal/gwt/client/ui/tree/VTree.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.tree; import java.util.ArrayList; import java.util.HashMap; @@ -39,12 +39,20 @@ import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.TooltipInfo; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Action; +import com.vaadin.terminal.gwt.client.ui.ActionOwner; +import com.vaadin.terminal.gwt.client.ui.FocusElementPanel; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.TreeAction; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; @@ -58,9 +66,9 @@ import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; /** * */ -public class VTree extends FocusElementPanel implements Paintable, - VHasDropHandler, FocusHandler, BlurHandler, KeyPressHandler, - KeyDownHandler, SubPartAware, ActionOwner { +public class VTree extends FocusElementPanel implements VHasDropHandler, + FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler, + SubPartAware, ActionOwner { public static final String CLASSNAME = "v-tree"; @@ -78,17 +86,17 @@ public class VTree extends FocusElementPanel implements Paintable, private static final int CHARCODE_SPACE = 32; - private final FlowPanel body = new FlowPanel(); + final FlowPanel body = new FlowPanel(); - private Set<String> selectedIds = new HashSet<String>(); - private ApplicationConnection client; - private String paintableId; - private boolean selectable; - private boolean isMultiselect; + Set<String> selectedIds = new HashSet<String>(); + ApplicationConnection client; + String paintableId; + boolean selectable; + boolean isMultiselect; private String currentMouseOverKey; - private TreeNode lastSelection; - private TreeNode focusedNode; - private int multiSelectMode = MULTISELECT_MODE_DEFAULT; + TreeNode lastSelection; + TreeNode focusedNode; + int multiSelectMode = MULTISELECT_MODE_DEFAULT; private final HashMap<String, TreeNode> keyToNode = new HashMap<String, TreeNode>(); @@ -98,23 +106,23 @@ public class VTree extends FocusElementPanel implements Paintable, */ private final HashMap<String, String> actionMap = new HashMap<String, String>(); - private boolean immediate; + boolean immediate; - private boolean isNullSelectionAllowed = true; + boolean isNullSelectionAllowed = true; - private boolean disabled = false; + boolean disabled = false; - private boolean readonly; + boolean readonly; - private boolean rendering; + boolean rendering; private VAbstractDropHandler dropHandler; - private int dragMode; + int dragMode; private boolean selectionHasChanged = false; - private String[] bodyActionKeys; + String[] bodyActionKeys; public VLazyExecutor iconLoaded = new VLazyExecutor(50, new ScheduledCommand() { @@ -213,24 +221,6 @@ public class VTree extends FocusElementPanel implements Paintable, } } - private void updateActionMap(UIDL c) { - final Iterator<?> it = c.getChildIterator(); - while (it.hasNext()) { - final UIDL action = (UIDL) it.next(); - final String key = action.getStringAttribute("key"); - final String caption = action.getStringAttribute("caption"); - actionMap.put(key + "_c", caption); - if (action.hasAttribute("icon")) { - // TODO need some uri handling ?? - actionMap.put(key + "_i", client.translateVaadinUri(action - .getStringAttribute("icon"))); - } else { - actionMap.remove(key + "_i"); - } - } - - } - public String getActionCaption(String actionKey) { return actionMap.get(actionKey + "_c"); } @@ -239,105 +229,6 @@ public class VTree extends FocusElementPanel implements Paintable, return actionMap.get(actionKey + "_i"); } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Ensure correct implementation and let container manage caption - if (client.updateComponent(this, uidl, true)) { - return; - } - - rendering = true; - - this.client = client; - - if (uidl.hasAttribute("partialUpdate")) { - handleUpdate(uidl); - rendering = false; - return; - } - - paintableId = uidl.getId(); - - immediate = uidl.hasAttribute("immediate"); - - disabled = uidl.getBooleanAttribute("disabled"); - readonly = uidl.getBooleanAttribute("readonly"); - - dragMode = uidl.hasAttribute("dragMode") ? uidl - .getIntAttribute("dragMode") : 0; - - isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect"); - - if (uidl.hasAttribute("alb")) { - bodyActionKeys = uidl.getStringArrayAttribute("alb"); - } - - body.clear(); - // clear out any references to nodes that no longer are attached - keyToNode.clear(); - TreeNode childTree = null; - UIDL childUidl = null; - for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) { - childUidl = (UIDL) i.next(); - if ("actions".equals(childUidl.getTag())) { - updateActionMap(childUidl); - continue; - } else if ("-ac".equals(childUidl.getTag())) { - updateDropHandler(childUidl); - continue; - } - childTree = new TreeNode(); - if (childTree.ie6compatnode != null) { - body.add(childTree); - } - childTree.updateFromUIDL(childUidl, client); - if (childTree.ie6compatnode == null) { - body.add(childTree); - } - childTree.addStyleDependentName("root"); - childTree.childNodeContainer.addStyleDependentName("root"); - } - if (childTree != null && childUidl != null) { - boolean leaf = !childUidl.getTag().equals("node"); - childTree.addStyleDependentName(leaf ? "leaf-last" : "last"); - childTree.childNodeContainer.addStyleDependentName("last"); - } - final String selectMode = uidl.getStringAttribute("selectmode"); - selectable = !"none".equals(selectMode); - isMultiselect = "multi".equals(selectMode); - - if (isMultiselect) { - if (BrowserInfo.get().isTouchDevice()) { - // Always use the simple mode for touch devices that do not have - // shift/ctrl keys (#8595) - multiSelectMode = MULTISELECT_MODE_SIMPLE; - } else { - multiSelectMode = uidl.getIntAttribute("multiselectmode"); - } - } - - selectedIds = uidl.getStringArrayVariableAsSet("selected"); - - // Update lastSelection and focusedNode to point to *actual* nodes again - // after the old ones have been cleared from the body. This fixes focus - // and keyboard navigation issues as described in #7057 and other - // tickets. - if (lastSelection != null) { - lastSelection = keyToNode.get(lastSelection.key); - } - if (focusedNode != null) { - setFocusedNode(keyToNode.get(focusedNode.key)); - } - - if (lastSelection == null && focusedNode == null - && !selectedIds.isEmpty()) { - setFocusedNode(keyToNode.get(selectedIds.iterator().next())); - focusedNode.setFocused(false); - } - - rendering = false; - - } - /** * Returns the first root node of the tree or null if there are no root * nodes. @@ -384,7 +275,7 @@ public class VTree extends FocusElementPanel implements Paintable, drag.getDropDetails().put("itemIdOver", currentMouseOverKey); if (currentMouseOverKey != null) { - TreeNode treeNode = keyToNode.get(currentMouseOverKey); + TreeNode treeNode = getNodeByKey(currentMouseOverKey); VerticalDropLocation detail = treeNode.getDropDetail(drag .getCurrentGwtEvent()); Boolean overTreeNode = null; @@ -406,7 +297,7 @@ public class VTree extends FocusElementPanel implements Paintable, return treeNode == null ? null : treeNode.key; } - private void updateDropHandler(UIDL childUidl) { + void updateDropHandler(UIDL childUidl) { if (dropHandler == null) { dropHandler = new VAbstractDropHandler() { @@ -448,7 +339,7 @@ public class VTree extends FocusElementPanel implements Paintable, .getDropDetails().get("detail"); if (curDetail == detail && newKey.equals(currentMouseOverKey)) { - keyToNode.get(newKey).emphasis(detail); + getNodeByKey(newKey).emphasis(detail); } /* * Else drag is already on a different @@ -470,7 +361,7 @@ public class VTree extends FocusElementPanel implements Paintable, private void cleanUp() { if (currentMouseOverKey != null) { - keyToNode.get(currentMouseOverKey).emphasis(null); + getNodeByKey(currentMouseOverKey).emphasis(null); currentMouseOverKey = null; } } @@ -482,8 +373,8 @@ public class VTree extends FocusElementPanel implements Paintable, } @Override - public Paintable getPaintable() { - return VTree.this; + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector(VTree.this); } public ApplicationConnection getApplicationConnection() { @@ -495,24 +386,12 @@ public class VTree extends FocusElementPanel implements Paintable, dropHandler.updateAcceptRules(childUidl); } - private void handleUpdate(UIDL uidl) { - final TreeNode rootNode = keyToNode.get(uidl - .getStringAttribute("rootKey")); - if (rootNode != null) { - if (!rootNode.getState()) { - // expanding node happened server side - rootNode.setState(true, false); - } - rootNode.renderChildNodes(uidl.getChildIterator()); - } - } - public void setSelected(TreeNode treeNode, boolean selected) { if (selected) { if (!isMultiselect) { while (selectedIds.size() > 0) { final String id = selectedIds.iterator().next(); - final TreeNode oldSelection = keyToNode.get(id); + final TreeNode oldSelection = getNodeByKey(id); if (oldSelection != null) { // can be null if the node is not visible (parent // collapsed) @@ -581,33 +460,26 @@ public class VTree extends FocusElementPanel implements Paintable, public String key; - private String[] actionKeys = null; + String[] actionKeys = null; - private boolean childrenLoaded; + boolean childrenLoaded; - private Element nodeCaptionDiv; + Element nodeCaptionDiv; protected Element nodeCaptionSpan; - private FlowPanel childNodeContainer; + FlowPanel childNodeContainer; private boolean open; private Icon icon; - private Element ie6compatnode; - private Event mouseDownEvent; private int cachedHeight = -1; private boolean focused = false; - /** - * Track onload events as IE6 sends two - */ - private boolean onloadHandled = false; - public TreeNode() { constructDom(); sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS @@ -705,11 +577,11 @@ public class VTree extends FocusElementPanel implements Paintable, // always when clicking an item, focus it setFocusedNode(this, false); - if (!isIE6OrOpera()) { + if (!BrowserInfo.get().isOpera()) { /* * Ensure that the tree's focus element also gains focus * (TreeNodes focus is faked using FocusElementPanel in browsers - * other than IE6 and Opera). + * other than Opera). */ focus(); } @@ -777,14 +649,7 @@ public class VTree extends FocusElementPanel implements Paintable, final Element target = DOM.eventGetTarget(event); if (type == Event.ONLOAD && target == icon.getElement()) { - if (onloadHandled) { - return; - } - if (BrowserInfo.get().isIE6()) { - fixWidth(); - } iconLoaded.trigger(); - onloadHandled = true; } if (disabled) { @@ -805,7 +670,7 @@ public class VTree extends FocusElementPanel implements Paintable, fireClick(event); } if (type == Event.ONCLICK) { - if (getElement() == target || ie6compatnode == target) { + if (getElement() == target) { // state change toggleState(); } else if (!readonly && inCaption) { @@ -860,7 +725,8 @@ public class VTree extends FocusElementPanel implements Paintable, if (mouseDownEvent != null) { // start actual drag on slight move when mouse is down VTransferable t = new VTransferable(); - t.setDragSource(VTree.this); + t.setDragSource(ConnectorMap.get(client).getConnector( + VTree.this)); t.setData("itemId", key); VDragEvent drag = VDragAndDropManager.get().startDrag( t, mouseDownEvent, true); @@ -891,7 +757,7 @@ public class VTree extends FocusElementPanel implements Paintable, * previously modified field may contain dirty variables. */ if (!treeHasFocus) { - if (isIE6OrOpera()) { + if (BrowserInfo.get().isOpera()) { if (focusedNode == null) { getNodeByKey(key).setFocused(true); } else { @@ -901,7 +767,8 @@ public class VTree extends FocusElementPanel implements Paintable, focus(); } } - final MouseEventDetails details = new MouseEventDetails(evt); + final MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(evt); ScheduledCommand command = new ScheduledCommand() { public void execute() { // Determine if we should send the event immediately to the @@ -952,15 +819,6 @@ public class VTree extends FocusElementPanel implements Paintable, protected void constructDom() { addStyleName(CLASSNAME); - // workaround for a very weird IE6 issue #1245 - if (BrowserInfo.get().isIE6()) { - ie6compatnode = DOM.createDiv(); - setStyleName(ie6compatnode, CLASSNAME + "-ie6compatnode"); - DOM.setInnerText(ie6compatnode, " "); - DOM.appendChild(getElement(), ie6compatnode); - - DOM.sinkEvents(ie6compatnode, Event.ONCLICK); - } nodeCaptionDiv = DOM.createDiv(); DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME @@ -972,7 +830,7 @@ public class VTree extends FocusElementPanel implements Paintable, DOM.appendChild(nodeCaptionDiv, wrapper); DOM.appendChild(wrapper, nodeCaptionSpan); - if (isIE6OrOpera()) { + if (BrowserInfo.get().isOpera()) { /* * Focus the caption div of the node to get keyboard navigation * to work without scrolling up or down when focusing a node. @@ -985,76 +843,6 @@ public class VTree extends FocusElementPanel implements Paintable, setWidget(childNodeContainer); } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - setText(uidl.getStringAttribute("caption")); - key = uidl.getStringAttribute("key"); - - keyToNode.put(key, this); - - if (uidl.hasAttribute("al")) { - actionKeys = uidl.getStringArrayAttribute("al"); - } - - if (uidl.getTag().equals("node")) { - if (uidl.getChildCount() == 0) { - childNodeContainer.setVisible(false); - } else { - renderChildNodes(uidl.getChildIterator()); - childrenLoaded = true; - } - } else { - addStyleName(CLASSNAME + "-leaf"); - } - if (uidl.hasAttribute("style")) { - addStyleName(CLASSNAME + "-" + uidl.getStringAttribute("style")); - Widget.setStyleName(nodeCaptionDiv, CLASSNAME + "-caption-" - + uidl.getStringAttribute("style"), true); - childNodeContainer.addStyleName(CLASSNAME + "-children-" - + uidl.getStringAttribute("style")); - } - - String description = uidl.getStringAttribute("descr"); - if (description != null && client != null) { - // Set tooltip - TooltipInfo info = new TooltipInfo(description); - client.registerTooltip(VTree.this, key, info); - } else { - // Remove possible previous tooltip - client.registerTooltip(VTree.this, key, null); - } - - if (uidl.getBooleanAttribute("expanded") && !getState()) { - setState(true, false); - } - - if (uidl.getBooleanAttribute("selected")) { - setSelected(true); - // ensure that identifier is in selectedIds array (this may be a - // partial update) - selectedIds.add(key); - } - - if (uidl.hasAttribute("icon")) { - if (icon == null) { - onloadHandled = false; - icon = new Icon(client); - DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv), - icon.getElement(), nodeCaptionSpan); - } - icon.setUri(uidl.getStringAttribute("icon")); - } else { - if (icon != null) { - DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv), - icon.getElement()); - icon = null; - } - } - - if (BrowserInfo.get().isIE6() && isAttached()) { - fixWidth(); - } - } - public boolean isLeaf() { String[] styleNames = getStyleName().split(" "); for (String styleName : styleNames) { @@ -1065,7 +853,7 @@ public class VTree extends FocusElementPanel implements Paintable, return false; } - private void setState(boolean state, boolean notifyServer) { + void setState(boolean state, boolean notifyServer) { if (open == state) { return; } @@ -1096,43 +884,14 @@ public class VTree extends FocusElementPanel implements Paintable, } } - private boolean getState() { + boolean getState() { return open; } - private void setText(String text) { + void setText(String text) { DOM.setInnerText(nodeCaptionSpan, text); } - private void renderChildNodes(Iterator<?> i) { - childNodeContainer.clear(); - childNodeContainer.setVisible(true); - while (i.hasNext()) { - final UIDL childUidl = (UIDL) i.next(); - // actions are in bit weird place, don't mix them with children, - // but current node's actions - if ("actions".equals(childUidl.getTag())) { - updateActionMap(childUidl); - continue; - } - final TreeNode childTree = new TreeNode(); - if (ie6compatnode != null) { - childNodeContainer.add(childTree); - } - childTree.updateFromUIDL(childUidl, client); - if (ie6compatnode == null) { - childNodeContainer.add(childTree); - } - if (!i.hasNext()) { - childTree - .addStyleDependentName(childTree.isLeaf() ? "leaf-last" - : "last"); - childTree.childNodeContainer.addStyleDependentName("last"); - } - } - childrenLoaded = true; - } - public boolean isChildrenLoaded() { return childrenLoaded; } @@ -1233,32 +992,6 @@ public class VTree extends FocusElementPanel implements Paintable, } /* - * We need to fix the width of TreeNodes so that the float in - * ie6compatNode does not wrap (see ticket #1245) - */ - private void fixWidth() { - nodeCaptionDiv.getStyle().setProperty("styleFloat", "left"); - nodeCaptionDiv.getStyle().setProperty("display", "inline"); - nodeCaptionDiv.getStyle().setProperty("marginLeft", "0"); - final int captionWidth = ie6compatnode.getOffsetWidth() - + nodeCaptionDiv.getOffsetWidth(); - setWidth(captionWidth + "px"); - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Widget#onAttach() - */ - @Override - public void onAttach() { - super.onAttach(); - if (ie6compatnode != null) { - fixWidth(); - } - } - - /* * (non-Javadoc) * * @see com.google.gwt.user.client.ui.Widget#onDetach() @@ -1288,19 +1021,14 @@ public class VTree extends FocusElementPanel implements Paintable, public void setFocused(boolean focused) { if (!this.focused && focused) { nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED); - if (BrowserInfo.get().isIE6()) { - ie6compatnode.addClassName(CLASSNAME_FOCUSED); - } + this.focused = focused; - if (isIE6OrOpera()) { + if (BrowserInfo.get().isOpera()) { nodeCaptionDiv.focus(); } treeHasFocus = true; } else if (this.focused && !focused) { nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED); - if (BrowserInfo.get().isIE6()) { - ie6compatnode.removeClassName(CLASSNAME_FOCUSED); - } this.focused = focused; treeHasFocus = false; } @@ -1313,6 +1041,34 @@ public class VTree extends FocusElementPanel implements Paintable, Util.scrollIntoViewVertically(nodeCaptionDiv); } + public void setIcon(String iconUrl) { + if (iconUrl != null) { + // Add icon if not present + if (icon == null) { + icon = new Icon(client); + DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv), + icon.getElement(), nodeCaptionSpan); + } + icon.setUri(iconUrl); + } else { + // Remove icon if present + if (icon != null) { + DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv), + icon.getElement()); + icon = null; + } + } + } + + public void setNodeStyleName(String styleName) { + addStyleName(TreeNode.CLASSNAME + "-" + styleName); + setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-" + + styleName, true); + childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-" + + styleName); + + } + } public VDropHandler getDropHandler() { @@ -2187,7 +1943,7 @@ public class VTree extends FocusElementPanel implements Paintable, */ public Element getSubPartElement(String subPart) { if ("fe".equals(subPart)) { - if (isIE6OrOpera() && focusedNode != null) { + if (BrowserInfo.get().isOpera() && focusedNode != null) { return focusedNode.getElement(); } return getFocusElement(); @@ -2219,11 +1975,7 @@ public class VTree extends FocusElementPanel implements Paintable, } if (expandCollapse) { - if (treeNode.ie6compatnode != null) { - return treeNode.ie6compatnode; - } else { - return treeNode.getElement(); - } + return treeNode.getElement(); } else { return treeNode.nodeCaptionSpan; } @@ -2267,8 +2019,7 @@ public class VTree extends FocusElementPanel implements Paintable, return null; } - if (subElement == treeNode.getElement() - || subElement == treeNode.ie6compatnode) { + if (subElement == treeNode.getElement()) { // Targets expand/collapse arrow isExpandCollapse = true; } @@ -2330,7 +2081,22 @@ public class VTree extends FocusElementPanel implements Paintable, } } - private boolean isIE6OrOpera() { - return BrowserInfo.get().isIE6() || BrowserInfo.get().isOpera(); + public void registerAction(String key, String caption, String iconUrl) { + actionMap.put(key + "_c", caption); + if (iconUrl != null) { + actionMap.put(key + "_i", iconUrl); + } else { + actionMap.remove(key + "_i"); + } + + } + + public void registerNode(TreeNode treeNode) { + keyToNode.put(treeNode.key, treeNode); } + + public void clearNodeToKeyMap() { + keyToNode.clear(); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/treetable/TreeTableConnector.java b/src/com/vaadin/terminal/gwt/client/ui/treetable/TreeTableConnector.java new file mode 100644 index 0000000000..f81771781b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/treetable/TreeTableConnector.java @@ -0,0 +1,102 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.treetable; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.FocusableScrollPanel; +import com.vaadin.terminal.gwt.client.ui.table.TableConnector; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.terminal.gwt.client.ui.treetable.VTreeTable.PendingNavigationEvent; +import com.vaadin.ui.TreeTable; + +@Connect(TreeTable.class) +public class TreeTableConnector extends TableConnector { + public static final String ATTRIBUTE_HIERARCHY_COLUMN_INDEX = "hci"; + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + FocusableScrollPanel widget = null; + int scrollPosition = 0; + if (getWidget().collapseRequest) { + widget = (FocusableScrollPanel) getWidget().getWidget(1); + scrollPosition = widget.getScrollPosition(); + } + getWidget().animationsEnabled = uidl.getBooleanAttribute("animate"); + getWidget().colIndexOfHierarchy = uidl + .hasAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl + .getIntAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) : 0; + int oldTotalRows = getWidget().getTotalRows(); + super.updateFromUIDL(uidl, client); + if (getWidget().collapseRequest) { + if (getWidget().collapsedRowKey != null + && getWidget().scrollBody != null) { + VScrollTableRow row = getWidget().getRenderedRowByKey( + getWidget().collapsedRowKey); + if (row != null) { + getWidget().setRowFocus(row); + getWidget().focus(); + } + } + + int scrollPosition2 = widget.getScrollPosition(); + if (scrollPosition != scrollPosition2) { + widget.setScrollPosition(scrollPosition); + } + + // check which rows are needed from the server and initiate a + // deferred fetch + getWidget().onScroll(null); + } + // Recalculate table size if collapse request, or if page length is zero + // (not sent by server) and row count changes (#7908). + if (getWidget().collapseRequest + || (!uidl.hasAttribute("pagelength") && getWidget() + .getTotalRows() != oldTotalRows)) { + /* + * Ensure that possibly removed/added scrollbars are considered. + * Triggers row calculations, removes cached rows etc. Basically + * cleans up state. Be careful if touching this, you will break + * pageLength=0 if you remove this. + */ + getWidget().triggerLazyColumnAdjustment(true); + + getWidget().collapseRequest = false; + } + if (uidl.hasAttribute("focusedRow")) { + String key = uidl.getStringAttribute("focusedRow"); + getWidget().setRowFocus(getWidget().getRenderedRowByKey(key)); + getWidget().focusParentResponsePending = false; + } else if (uidl.hasAttribute("clearFocusPending")) { + // Special case to detect a response to a focusParent request that + // does not return any focusedRow because the selected node has no + // parent + getWidget().focusParentResponsePending = false; + } + + while (!getWidget().collapseRequest + && !getWidget().focusParentResponsePending + && !getWidget().pendingNavigationEvents.isEmpty()) { + // Keep replaying any queued events as long as we don't have any + // potential content changes pending + PendingNavigationEvent event = getWidget().pendingNavigationEvents + .removeFirst(); + getWidget() + .handleNavigation(event.keycode, event.ctrl, event.shift); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTreeTable.class); + } + + @Override + public VTreeTable getWidget() { + return (VTreeTable) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java index 8e55400f31..2e15e7c445 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.treetable; import java.util.ArrayList; import java.util.Iterator; @@ -24,21 +24,19 @@ import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.ComputedStyle; -import com.vaadin.terminal.gwt.client.RenderSpace; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; -import com.vaadin.terminal.gwt.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable; +import com.vaadin.terminal.gwt.client.ui.treetable.VTreeTable.VTreeTableScrollBody.VTreeTableRow; public class VTreeTable extends VScrollTable { - private static class PendingNavigationEvent { - private final int keycode; - private final boolean ctrl; - private final boolean shift; + static class PendingNavigationEvent { + final int keycode; + final boolean ctrl; + final boolean shift; public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) { this.keycode = keycode; @@ -59,82 +57,14 @@ public class VTreeTable extends VScrollTable { } } - public static final String ATTRIBUTE_HIERARCHY_COLUMN_INDEX = "hci"; - private boolean collapseRequest; + boolean collapseRequest; private boolean selectionPending; - private int colIndexOfHierarchy; - private String collapsedRowKey; - private VTreeTableScrollBody scrollBody; - private boolean animationsEnabled; - private LinkedList<PendingNavigationEvent> pendingNavigationEvents = new LinkedList<VTreeTable.PendingNavigationEvent>(); - private boolean focusParentResponsePending; - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - FocusableScrollPanel widget = null; - int scrollPosition = 0; - if (collapseRequest) { - widget = (FocusableScrollPanel) getWidget(1); - scrollPosition = widget.getScrollPosition(); - } - animationsEnabled = uidl.getBooleanAttribute("animate"); - colIndexOfHierarchy = uidl - .hasAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl - .getIntAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) : 0; - int oldTotalRows = getTotalRows(); - super.updateFromUIDL(uidl, client); - if (collapseRequest) { - if (collapsedRowKey != null && scrollBody != null) { - VScrollTableRow row = getRenderedRowByKey(collapsedRowKey); - if (row != null) { - setRowFocus(row); - focus(); - } - } - - int scrollPosition2 = widget.getScrollPosition(); - if (scrollPosition != scrollPosition2) { - widget.setScrollPosition(scrollPosition); - } - - // check which rows are needed from the server and initiate a - // deferred fetch - onScroll(null); - } - // Recalculate table size if collapse request, or if page length is zero - // (not sent by server) and row count changes (#7908). - if (collapseRequest - || (!uidl.hasAttribute("pagelength") && getTotalRows() != oldTotalRows)) { - /* - * Ensure that possibly removed/added scrollbars are considered. - * Triggers row calculations, removes cached rows etc. Basically - * cleans up state. Be careful if touching this, you will break - * pageLength=0 if you remove this. - */ - triggerLazyColumnAdjustment(true); - - collapseRequest = false; - } - if (uidl.hasAttribute("focusedRow")) { - String key = uidl.getStringAttribute("focusedRow"); - setRowFocus(getRenderedRowByKey(key)); - focusParentResponsePending = false; - } else if (uidl.hasAttribute("clearFocusPending")) { - // Special case to detect a response to a focusParent request that - // does not return any focusedRow because the selected node has no - // parent - focusParentResponsePending = false; - } - - while (!collapseRequest && !focusParentResponsePending - && !pendingNavigationEvents.isEmpty()) { - // Keep replaying any queued events as long as we don't have any - // potential content changes pending - PendingNavigationEvent event = pendingNavigationEvents - .removeFirst(); - handleNavigation(event.keycode, event.ctrl, event.shift); - } - } + int colIndexOfHierarchy; + String collapsedRowKey; + VTreeTableScrollBody scrollBody; + boolean animationsEnabled; + LinkedList<PendingNavigationEvent> pendingNavigationEvents = new LinkedList<VTreeTable.PendingNavigationEvent>(); + boolean focusParentResponsePending; @Override protected VScrollTableBody createScrollBody() { @@ -169,7 +99,7 @@ public class VTreeTable extends VScrollTable { private boolean browserSupportsAnimation() { BrowserInfo bi = BrowserInfo.get(); - return !(bi.isIE6() || bi.isIE7() || bi.isSafari4()); + return !(bi.isSafari4()); } class VTreeTableScrollBody extends VScrollTable.VScrollTableBody { @@ -293,29 +223,6 @@ public class VTreeTable extends VScrollTable { } } - @Override - public RenderSpace getAllocatedSpace(Widget child) { - if (widgetInHierarchyColumn == child) { - final int hierarchyAndIconWidth = getHierarchyAndIconWidth(); - final RenderSpace allocatedSpace = super - .getAllocatedSpace(child); - return new RenderSpace() { - @Override - public int getWidth() { - return allocatedSpace.getWidth() - - hierarchyAndIconWidth; - } - - @Override - public int getHeight() { - return allocatedSpace.getHeight(); - } - - }; - } - return super.getAllocatedSpace(child); - } - private int getHierarchyAndIconWidth() { int consumedSpace = treeSpacer.getOffsetWidth(); if (treeSpacer.getParentElement().getChildCount() > 2) { @@ -894,4 +801,5 @@ public class VTreeTable extends VScrollTable { int newTotalRows = uidl.getIntAttribute("totalrows"); setTotalRows(newTotalRows); } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/twincolselect/TwinColSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/TwinColSelectConnector.java new file mode 100644 index 0000000000..328d0fc18b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/TwinColSelectConnector.java @@ -0,0 +1,69 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.twincolselect; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.ui.TwinColSelect; + +@Connect(TwinColSelect.class) +public class TwinColSelectConnector extends OptionGroupBaseConnector implements + DirectionalManagedLayout { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Captions are updated before super call to ensure the widths are set + // correctly + if (isRealUpdate(uidl)) { + getWidget().updateCaptions(uidl); + getLayoutManager().setNeedsHorizontalLayout(this); + } + + super.updateFromUIDL(uidl, client); + } + + @Override + protected void init() { + getLayoutManager().registerDependency(this, + getWidget().captionWrapper.getElement()); + } + + @Override + public void onUnregister() { + getLayoutManager().unregisterDependency(this, + getWidget().captionWrapper.getElement()); + } + + @Override + protected Widget createWidget() { + return GWT.create(VTwinColSelect.class); + } + + @Override + public VTwinColSelect getWidget() { + return (VTwinColSelect) super.getWidget(); + } + + public void layoutVertically() { + if (isUndefinedHeight()) { + getWidget().clearInternalHeights(); + } else { + getWidget().setInternalHeights(); + } + } + + public void layoutHorizontally() { + if (isUndefinedWidth()) { + getWidget().clearInternalWidths(); + } else { + getWidget().setInternalWidths(); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/VTwinColSelect.java index b569dbc94e..8f1ea09b8f 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/VTwinColSelect.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.twincolselect; import java.util.ArrayList; import java.util.HashSet; @@ -26,10 +26,11 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.Panel; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.button.VButton; +import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroupBase; public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, MouseDownHandler, DoubleClickHandler, SubPartAware { @@ -46,7 +47,7 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, private final DoubleClickListBox selections; - private FlowPanel captionWrapper; + FlowPanel captionWrapper; private HTML optionsCaption = null; @@ -60,8 +61,6 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, private final Panel panel; - private boolean widthSet = false; - /** * A ListBox which catches double clicks * @@ -157,18 +156,7 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, return selectionsCaption; } - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Captions are updated before super call to ensure the widths are set - // correctly - if (!uidl.getBooleanAttribute("cached")) { - updateCaptions(uidl); - } - - super.updateFromUIDL(uidl, client); - } - - private void updateCaptions(UIDL uidl) { + protected void updateCaptions(UIDL uidl) { String leftCaption = (uidl.hasAttribute(ATTRIBUTE_LEFT_CAPTION) ? uidl .getStringAttribute(ATTRIBUTE_LEFT_CAPTION) : null); String rightCaption = (uidl.hasAttribute(ATTRIBUTE_RIGHT_CAPTION) ? uidl @@ -238,32 +226,6 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, } } - int cols = -1; - if (getColumns() > 0) { - cols = getColumns(); - } else if (!widthSet) { - cols = DEFAULT_COLUMN_COUNT; - } - - if (cols >= 0) { - String colWidth = cols + "em"; - String containerWidth = (2 * cols + 4) + "em"; - // Caption wrapper width == optionsSelect + buttons + - // selectionsSelect - String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em"; - - options.setWidth(colWidth); - if (optionsCaption != null) { - optionsCaption.setWidth(colWidth); - } - selections.setWidth(colWidth); - if (selectionsCaption != null) { - selectionsCaption.setWidth(colWidth); - } - buttons.setWidth("3.5em"); - optionsContainer.setWidth(containerWidth); - captionWrapper.setWidth(captionWrapperWidth); - } if (getRows() > 0) { options.setVisibleItemCount(getRows()); selections.setVisibleItemCount(getRows()); @@ -297,7 +259,7 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, Set<String> movedItems = moveSelectedItems(options, selections); selectedKeys.addAll(movedItems); - client.updateVariable(id, "selected", + client.updateVariable(paintableId, "selected", selectedKeys.toArray(new String[selectedKeys.size()]), isImmediate()); } @@ -306,7 +268,7 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, Set<String> movedItems = moveSelectedItems(selections, options); selectedKeys.removeAll(movedItems); - client.updateVariable(id, "selected", + client.updateVariable(paintableId, "selected", selectedKeys.toArray(new String[selectedKeys.size()]), isImmediate()); } @@ -372,35 +334,15 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, } } - @Override - public void setHeight(String height) { - super.setHeight(height); - if ("".equals(height)) { - options.setHeight(""); - selections.setHeight(""); - } else { - setInternalHeights(); - } + void clearInternalHeights() { + selections.setHeight(""); + options.setHeight(""); } - private void setInternalHeights() { - int captionHeight = 0; - int totalHeight; - if (BrowserInfo.get().isIE6()) { - String o = getElement().getStyle().getOverflow(); + void setInternalHeights() { + int captionHeight = Util.getRequiredHeight(captionWrapper); + int totalHeight = getOffsetHeight(); - getElement().getStyle().setOverflow(Overflow.HIDDEN); - totalHeight = getOffsetHeight(); - getElement().getStyle().setProperty("overflow", o); - } else { - totalHeight = getOffsetHeight(); - } - - if (optionsCaption != null) { - captionHeight = Util.getRequiredHeight(optionsCaption); - } else if (selectionsCaption != null) { - captionHeight = Util.getRequiredHeight(selectionsCaption); - } String selectHeight = (totalHeight - captionHeight) + "px"; selections.setHeight(selectHeight); @@ -408,27 +350,40 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, } - @Override - public void setWidth(String width) { - super.setWidth(width); - if (!"".equals(width) && width != null) { - setInternalWidths(); - widthSet = true; + void clearInternalWidths() { + int cols = -1; + if (getColumns() > 0) { + cols = getColumns(); } else { - widthSet = false; + cols = DEFAULT_COLUMN_COUNT; + } + + if (cols >= 0) { + String colWidth = cols + "em"; + String containerWidth = (2 * cols + 4) + "em"; + // Caption wrapper width == optionsSelect + buttons + + // selectionsSelect + String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em"; + + options.setWidth(colWidth); + if (optionsCaption != null) { + optionsCaption.setWidth(colWidth); + } + selections.setWidth(colWidth); + if (selectionsCaption != null) { + selectionsCaption.setWidth(colWidth); + } + buttons.setWidth("3.5em"); + optionsContainer.setWidth(containerWidth); + captionWrapper.setWidth(captionWrapperWidth); } } - private void setInternalWidths() { + void setInternalWidths() { DOM.setStyleAttribute(getElement(), "position", "relative"); int bordersAndPaddings = Util.measureHorizontalPaddingAndBorder( buttons.getElement(), 0); - if (BrowserInfo.get().isIE6()) { - // IE6 sets a border on selects by default.. - bordersAndPaddings += 4; - } - int buttonWidth = Util.getRequiredWidth(buttons); int totalWidth = getOffsetWidth(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadConnector.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadConnector.java new file mode 100644 index 0000000000..0a2c0b241e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadConnector.java @@ -0,0 +1,67 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.upload; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.Upload; + +@Connect(value = Upload.class, loadStyle = LoadStyle.LAZY) +public class UploadConnector extends AbstractComponentConnector implements + Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + if (uidl.hasAttribute("notStarted")) { + getWidget().t.schedule(400); + return; + } + if (uidl.hasAttribute("forceSubmit")) { + getWidget().submit(); + return; + } + getWidget().setImmediate(getState().isImmediate()); + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + getWidget().nextUploadId = uidl.getIntAttribute("nextid"); + final String action = client.translateVaadinUri(uidl + .getStringVariable("action")); + getWidget().element.setAction(action); + if (uidl.hasAttribute("buttoncaption")) { + getWidget().submitButton.setText(uidl + .getStringAttribute("buttoncaption")); + getWidget().submitButton.setVisible(true); + } else { + getWidget().submitButton.setVisible(false); + } + getWidget().fu.setName(getWidget().paintableId + "_file"); + + if (!isEnabled() || isReadOnly()) { + getWidget().disableUpload(); + } else if (!uidl.getBooleanAttribute("state")) { + // Enable the button only if an upload is not in progress + getWidget().enableUpload(); + getWidget().ensureTargetFrame(); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VUpload.class); + } + + @Override + public VUpload getWidget() { + return (VUpload) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategy.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java index 455a9bf601..18cfc643d3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategy.java +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java @@ -1,16 +1,16 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.upload; public class UploadIFrameOnloadStrategy { native void hookEvents(com.google.gwt.dom.client.Element iframe, VUpload upload) /*-{ - iframe.onload = $entry(function() { - upload.@com.vaadin.terminal.gwt.client.ui.VUpload::onSubmitComplete()(); - }); + iframe.onload = function() { + upload.@com.vaadin.terminal.gwt.client.ui.upload.VUpload::onSubmitComplete()(); + }; }-*/; /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategyIE.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java index b23d82fa22..19d38a8a95 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategyIE.java +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java @@ -1,7 +1,7 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.upload; import com.google.gwt.dom.client.Element; @@ -13,11 +13,11 @@ public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy { @Override native void hookEvents(Element iframe, VUpload upload) /*-{ - iframe.onreadystatechange = $entry(function() { + iframe.onreadystatechange = function() { if (iframe.readyState == 'complete') { - upload.@com.vaadin.terminal.gwt.client.ui.VUpload::onSubmitComplete()(); + upload.@com.vaadin.terminal.gwt.client.ui.upload.VUpload::onSubmitComplete()(); } - }); + }; }-*/; @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java b/src/com/vaadin/terminal/gwt/client/ui/upload/VUpload.java index f3105b70a1..4fe53fb89c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/VUpload.java @@ -2,7 +2,7 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.upload; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; @@ -23,10 +23,9 @@ import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.SimplePanel; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.button.VButton; /** * @@ -34,7 +33,7 @@ import com.vaadin.terminal.gwt.client.VTooltip; * events even though the upload component is already detached. * */ -public class VUpload extends SimplePanel implements Paintable { +public class VUpload extends SimplePanel { private final class MyFileUpload extends FileUpload { @Override @@ -72,19 +71,19 @@ public class VUpload extends SimplePanel implements Paintable { ApplicationConnection client; - private String paintableId; + protected String paintableId; /** * Button that initiates uploading */ - private final VButton submitButton; + protected final VButton submitButton; /** * When expecting big files, programmer may initiate some UI changes when * uploading the file starts. Bit after submitting file we'll visit the * server to check possible changes. */ - private Timer t; + protected Timer t; /** * some browsers tries to send form twice if submit is called in button @@ -99,11 +98,11 @@ public class VUpload extends SimplePanel implements Paintable { private Hidden maxfilesize = new Hidden(); - private FormElement element; + protected FormElement element; private com.google.gwt.dom.client.Element synthesizedFrame; - private int nextUploadId; + protected int nextUploadId; public VUpload() { super(com.google.gwt.dom.client.Document.get().createFormElement()); @@ -144,47 +143,9 @@ public class VUpload extends SimplePanel implements Paintable { private static native void setEncoding(Element form, String encoding) /*-{ form.enctype = encoding; - // For IE6 - form.encoding = encoding; }-*/; - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, true)) { - return; - } - if (uidl.hasAttribute("notStarted")) { - t.schedule(400); - return; - } - if (uidl.hasAttribute("forceSubmit")) { - submit(); - return; - } - setImmediate(uidl.getBooleanAttribute("immediate")); - this.client = client; - paintableId = uidl.getId(); - nextUploadId = uidl.getIntAttribute("nextid"); - final String action = client.translateVaadinUri(uidl - .getStringVariable("action")); - element.setAction(action); - if (uidl.hasAttribute("buttoncaption")) { - submitButton.setText(uidl.getStringAttribute("buttoncaption")); - submitButton.setVisible(true); - } else { - submitButton.setVisible(false); - } - fu.setName(paintableId + "_file"); - - if (uidl.hasAttribute("disabled") || uidl.hasAttribute("readonly")) { - disableUpload(); - } else if (!uidl.getBooleanAttribute("state")) { - // Enable the button only if an upload is not in progress - enableUpload(); - ensureTargetFrame(); - } - } - - private void setImmediate(boolean booleanAttribute) { + protected void setImmediate(boolean booleanAttribute) { if (immediate != booleanAttribute) { immediate = booleanAttribute; if (immediate) { @@ -278,7 +239,7 @@ public class VUpload extends SimplePanel implements Paintable { }); } - private void submit() { + protected void submit() { if (fu.getFilename().length() == 0 || submitted || !enabled) { VConsole.log("Submit cancelled (disabled, no file or already submitted)"); return; @@ -316,15 +277,12 @@ public class VUpload extends SimplePanel implements Paintable { } } - private void ensureTargetFrame() { + protected void ensureTargetFrame() { if (synthesizedFrame == null) { // Attach a hidden IFrame to the form. This is the target iframe to - // which - // the form will be submitted. We have to create the iframe using - // innerHTML, - // because setting an iframe's 'name' property dynamically doesn't - // work on - // most browsers. + // which the form will be submitted. We have to create the iframe + // using innerHTML, because setting an iframe's 'name' property + // dynamically doesn't work on most browsers. DivElement dummy = Document.get().createDivElement(); dummy.setInnerHTML("<iframe src=\"javascript:''\" name='" + getFrameName() @@ -355,5 +313,4 @@ public class VUpload extends SimplePanel implements Paintable { synthesizedFrame = null; } } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VVideo.java b/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java index 8599ffb279..484000b8d1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VVideo.java +++ b/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java @@ -2,18 +2,16 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.video; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.VideoElement; import com.google.gwt.user.client.Element; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.VMediaBase; public class VVideo extends VMediaBase { - public static final String ATTR_POSTER = "poster"; private static String CLASSNAME = "v-video"; @@ -27,22 +25,6 @@ public class VVideo extends VMediaBase { updateDimensionsWhenMetadataLoaded(getElement()); } - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, true)) { - return; - } - super.updateFromUIDL(uidl, client); - setPosterFromUIDL(uidl); - } - - private void setPosterFromUIDL(UIDL uidl) { - if (uidl.hasAttribute(ATTR_POSTER)) { - video.setPoster(client.translateVaadinUri(uidl - .getStringAttribute(ATTR_POSTER))); - } - } - /** * Registers a listener that updates the dimensions of the widget when the * video metadata has been loaded. @@ -53,7 +35,7 @@ public class VVideo extends VMediaBase { /*-{ var self = this; el.addEventListener('loadedmetadata', $entry(function(e) { - self.@com.vaadin.terminal.gwt.client.ui.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight); + self.@com.vaadin.terminal.gwt.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight); }), false); }-*/; @@ -74,4 +56,9 @@ public class VVideo extends VMediaBase { protected String getDefaultAltHtml() { return "Your browser does not support the <code>video</code> element."; } + + public void setPoster(String poster) { + video.setPoster(poster); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/video/VideoConnector.java b/src/com/vaadin/terminal/gwt/client/ui/video/VideoConnector.java new file mode 100644 index 0000000000..ec763fff07 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/video/VideoConnector.java @@ -0,0 +1,46 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.video; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.MediaBaseConnector; +import com.vaadin.ui.Video; + +@Connect(Video.class) +public class VideoConnector extends MediaBaseConnector { + public static final String ATTR_POSTER = "poster"; + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + if (!isRealUpdate(uidl)) { + return; + } + super.updateFromUIDL(uidl, client); + setPosterFromUIDL(uidl); + } + + private void setPosterFromUIDL(UIDL uidl) { + if (uidl.hasAttribute(ATTR_POSTER)) { + getWidget().setPoster( + getConnection().translateVaadinUri( + uidl.getStringAttribute(ATTR_POSTER))); + } + } + + @Override + public VVideo getWidget() { + return (VVideo) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VVideo.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VWindow.java b/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java index 103979927a..d08387fc6d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VWindow.java +++ b/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java @@ -2,57 +2,52 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.client.ui; +package com.vaadin.terminal.gwt.client.ui.window; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.Iterator; -import java.util.Set; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.DomEvent.Type; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Frame; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.Console; -import com.vaadin.terminal.gwt.client.Container; import com.vaadin.terminal.gwt.client.EventId; import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.RenderSpace; -import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.LayoutManager; import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.terminal.gwt.client.ui.FocusableScrollPanel; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.VOverlay; /** * "Sub window" component. * * @author Vaadin Ltd */ -public class VWindow extends VOverlay implements Container, - ShortcutActionHandlerOwner, ScrollHandler, KeyDownHandler, - FocusHandler, BlurHandler, BeforeShortcutActionListener, Focusable { +public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, + ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable { /** * Minimum allowed height of a window. This refers to the content area, not @@ -72,34 +67,21 @@ public class VWindow extends VOverlay implements Container, public static final String CLASSNAME = "v-window"; - /** - * Difference between offsetWidth and inner width for the content area. - */ - private int contentAreaBorderPadding = -1; - /** - * Pixels used by inner borders and paddings horizontally (calculated only - * once). This is the difference between the width of the root element and - * the content area, such that if root element width is set to "XYZpx" the - * inner width (width-border-padding) of the content area is - * X-contentAreaRootDifference. - */ - private int contentAreaToRootDifference = -1; - private static final int STACKING_OFFSET_PIXELS = 15; public static final int Z_INDEX = 10000; - private Paintable layout; + ComponentConnector layout; - private Element contents; + Element contents; - private Element header; + Element header; - private Element footer; + Element footer; private Element resizeBox; - private final FocusableScrollPanel contentPanel = new FocusableScrollPanel(); + final FocusableScrollPanel contentPanel = new FocusableScrollPanel(); private boolean dragging; @@ -117,11 +99,11 @@ public class VWindow extends VOverlay implements Container, private int origH; - private Element closeBox; + Element closeBox; protected ApplicationConnection client; - private String id; + String id; ShortcutActionHandler shortcutHandler; @@ -131,13 +113,13 @@ public class VWindow extends VOverlay implements Container, /** Last known positiony read from UIDL or updated to application connection */ private int uidlPositionY = -1; - private boolean vaadinModality = false; + boolean vaadinModality = false; - private boolean resizable = true; + boolean resizable = true; private boolean draggable = true; - private boolean resizeLazy = false; + boolean resizeLazy = false; private Element modalityCurtain; private Element draggingCurtain; @@ -147,40 +129,18 @@ public class VWindow extends VOverlay implements Container, private boolean closable = true; - boolean dynamicWidth = false; - boolean dynamicHeight = false; - boolean layoutRelativeWidth = false; - boolean layoutRelativeHeight = false; - // If centered (via UIDL), the window should stay in the centered -mode // until a position is received from the server, or the user moves or // resizes the window. boolean centered = false; - private RenderSpace renderSpace = new RenderSpace(MIN_CONTENT_AREA_WIDTH, - MIN_CONTENT_AREA_HEIGHT, true); - - private String width; + boolean immediate; - private String height; + private Element wrapper; - private boolean immediate; + boolean visibilityChangesDisabled; - private Element wrapper, wrapper2; - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this, - VPanel.CLICK_EVENT_IDENTIFIER) { - - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); - } - }; - - private boolean visibilityChangesDisabled; - - private int bringToFrontSequence = -1; + int bringToFrontSequence = -1; private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200, new ScheduledCommand() { @@ -195,12 +155,7 @@ public class VWindow extends VOverlay implements Container, // Different style of shadow for windows setShadowStyle("window"); - final int order = windowOrder.size(); - setWindowOrder(order); - windowOrder.add(this); constructDOM(); - setPopupPosition(order * STACKING_OFFSET_PIXELS, order - * STACKING_OFFSET_PIXELS); contentPanel.addScrollHandler(this); contentPanel.addKeyDownHandler(this); contentPanel.addFocusHandler(this); @@ -224,7 +179,26 @@ public class VWindow extends VOverlay implements Container, * @return */ private boolean isActive() { - return windowOrder.get(windowOrder.size() - 1).equals(this); + return equals(getTopmostWindow()); + } + + private static VWindow getTopmostWindow() { + return windowOrder.get(windowOrder.size() - 1); + } + + void setWindowOrderAndPosition() { + // This cannot be done in the constructor as the widgets are created in + // a different order than on they should appear on screen + if (windowOrder.contains(this)) { + // Already set + return; + } + final int order = windowOrder.size(); + setWindowOrder(order); + windowOrder.add(this); + setPopupPosition(order * STACKING_OFFSET_PIXELS, order + * STACKING_OFFSET_PIXELS); + } private void setWindowOrder(int order) { @@ -267,15 +241,11 @@ public class VWindow extends VOverlay implements Container, wrapper = DOM.createDiv(); DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap"); - wrapper2 = DOM.createDiv(); - DOM.setElementProperty(wrapper2, "className", CLASSNAME + "-wrap2"); - - DOM.appendChild(wrapper2, closeBox); - DOM.appendChild(wrapper2, header); + DOM.appendChild(wrapper, header); + DOM.appendChild(wrapper, closeBox); DOM.appendChild(header, headerText); - DOM.appendChild(wrapper2, contents); - DOM.appendChild(wrapper2, footer); - DOM.appendChild(wrapper, wrapper2); + DOM.appendChild(wrapper, contents); + DOM.appendChild(wrapper, footer); DOM.appendChild(super.getContainerElement(), wrapper); sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK @@ -285,236 +255,12 @@ public class VWindow extends VOverlay implements Container, } - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - id = uidl.getId(); - this.client = client; - - // Workaround needed for Testing Tools (GWT generates window DOM - // slightly different in different browsers). - DOM.setElementProperty(closeBox, "id", id + "_window_close"); - - if (uidl.hasAttribute("invisible")) { - hide(); - return; - } - - if (!uidl.hasAttribute("cached")) { - if (uidl.getBooleanAttribute("modal") != vaadinModality) { - setVaadinModality(!vaadinModality); - } - if (!isAttached()) { - setVisible(false); // hide until possible centering - show(); - } - if (uidl.getBooleanAttribute("resizable") != resizable) { - setResizable(!resizable); - } - resizeLazy = uidl.hasAttribute(VView.RESIZE_LAZY); - - setDraggable(!uidl.hasAttribute("fixedposition")); - - // Caption must be set before required header size is measured. If - // the caption attribute is missing the caption should be cleared. - setCaption(uidl.getStringAttribute("caption"), - uidl.getStringAttribute("icon")); - } - - visibilityChangesDisabled = true; - if (client.updateComponent(this, uidl, false)) { - return; - } - visibilityChangesDisabled = false; - - clickEventHandler.handleEventHandlerRegistration(client); - - immediate = uidl.hasAttribute("immediate"); - - setClosable(!uidl.getBooleanAttribute("readonly")); - - // Initialize the position form UIDL - int positionx = uidl.getIntVariable("positionx"); - int positiony = uidl.getIntVariable("positiony"); - if (positionx >= 0 || positiony >= 0) { - if (positionx < 0) { - positionx = 0; - } - if (positiony < 0) { - positiony = 0; - } - setPopupPosition(positionx, positiony); - } - - boolean showingUrl = false; - int childIndex = 0; - UIDL childUidl = uidl.getChildUIDL(childIndex++); - while ("open".equals(childUidl.getTag())) { - // TODO multiple opens with the same target will in practice just - // open the last one - should we fix that somehow? - final String parsedUri = client.translateVaadinUri(childUidl - .getStringAttribute("src")); - if (!childUidl.hasAttribute("name")) { - final Frame frame = new Frame(); - DOM.setStyleAttribute(frame.getElement(), "width", "100%"); - DOM.setStyleAttribute(frame.getElement(), "height", "100%"); - DOM.setStyleAttribute(frame.getElement(), "border", "0px"); - frame.setUrl(parsedUri); - contentPanel.setWidget(frame); - showingUrl = true; - } else { - final String target = childUidl.getStringAttribute("name"); - Window.open(parsedUri, target, ""); - } - childUidl = uidl.getChildUIDL(childIndex++); - } - - final Paintable lo = client.getPaintable(childUidl); - if (layout != null) { - if (layout != lo) { - // remove old - client.unregisterPaintable(layout); - contentPanel.remove((Widget) layout); - // add new - if (!showingUrl) { - contentPanel.setWidget((Widget) lo); - } - layout = lo; - } - } else if (!showingUrl) { - contentPanel.setWidget((Widget) lo); - layout = lo; - } - - dynamicWidth = !uidl.hasAttribute("width"); - dynamicHeight = !uidl.hasAttribute("height"); - - layoutRelativeWidth = uidl.hasAttribute("layoutRelativeWidth"); - layoutRelativeHeight = uidl.hasAttribute("layoutRelativeHeight"); - - if (dynamicWidth && layoutRelativeWidth) { - /* - * Relative layout width, fix window width before rendering (width - * according to caption) - */ - setNaturalWidth(); - } - - layout.updateFromUIDL(childUidl, client); - if (!dynamicHeight && layoutRelativeWidth) { - /* - * Relative layout width, and fixed height. Must update the size to - * be able to take scrollbars into account (layout gets narrower - * space if it is higher than the window) -> only vertical scrollbar - */ - client.runDescendentsLayout(this); - } - - /* - * No explicit width is set and the layout does not have relative width - * so fix the size according to the layout. - */ - if (dynamicWidth && !layoutRelativeWidth) { - setNaturalWidth(); - } - - if (dynamicHeight && layoutRelativeHeight) { - // Prevent resizing until height has been fixed - resizable = false; - } - - // we may have actions and notifications - if (uidl.getChildCount() > 1) { - final int cnt = uidl.getChildCount(); - for (int i = 1; i < cnt; i++) { - childUidl = uidl.getChildUIDL(i); - if (childUidl.getTag().equals("actions")) { - if (shortcutHandler == null) { - shortcutHandler = new ShortcutActionHandler(id, client); - } - shortcutHandler.updateActionMap(childUidl); - } else if (childUidl.getTag().equals("notifications")) { - for (final Iterator<?> it = childUidl.getChildIterator(); it - .hasNext();) { - final UIDL notification = (UIDL) it.next(); - VNotification.showNotification(client, notification); - } - } - } - - } - - // setting scrollposition must happen after children is rendered - contentPanel.setScrollPosition(uidl.getIntVariable("scrollTop")); - contentPanel.setHorizontalScrollPosition(uidl - .getIntVariable("scrollLeft")); - - // Center this window on screen if requested - // This has to be here because we might not know the content size before - // everything is painted into the window - if (uidl.getBooleanAttribute("center")) { - // mark as centered - this is unset on move/resize - centered = true; - center(); - } else { - // don't try to center the window anymore - centered = false; - } - updateShadowSizeAndPosition(); - setVisible(true); - - boolean sizeReduced = false; - // ensure window is not larger than browser window - if (getOffsetWidth() > Window.getClientWidth()) { - setWidth(Window.getClientWidth() + "px"); - sizeReduced = true; - } - if (getOffsetHeight() > Window.getClientHeight()) { - setHeight(Window.getClientHeight() + "px"); - sizeReduced = true; - } - - if (dynamicHeight && layoutRelativeHeight) { - /* - * Window height is undefined, layout is 100% high so the layout - * should define the initial window height but on resize the layout - * should be as high as the window. We fix the height to deal with - * this. - */ - - int h = contents.getOffsetHeight() + getExtraHeight(); - int w = getElement().getOffsetWidth(); - - client.updateVariable(id, "height", h, false); - client.updateVariable(id, "width", w, true); - } - - if (sizeReduced) { - // If we changed the size we need to update the size of the child - // component if it is relative (#3407) - client.runDescendentsLayout(this); - } - - Util.runWebkitOverflowAutoFix(contentPanel.getElement()); - - client.getView().scrollIntoView(uidl); - - if (uidl.hasAttribute("bringToFront")) { - /* - * Focus as a side-efect. Will be overridden by - * ApplicationConnection if another component was focused by the - * server side. - */ - contentPanel.focus(); - bringToFrontSequence = uidl.getIntAttribute("bringToFront"); - deferOrdering(); - } - } - /** * Calling this method will defer ordering algorithm, to order windows based * on servers bringToFront and modality instructions. Non changed windows * will be left intact. */ - private static void deferOrdering() { + static void deferOrdering() { if (!orderingDefered) { orderingDefered = true; Scheduler.get().scheduleFinally(new Command() { @@ -569,7 +315,7 @@ public class VWindow extends VOverlay implements Container, } } - private void setDraggable(boolean draggable) { + void setDraggable(boolean draggable) { if (this.draggable == draggable) { return; } @@ -589,69 +335,6 @@ public class VWindow extends VOverlay implements Container, } } - private void setNaturalWidth() { - /* - * Use max(layout width, window width) i.e layout content width or - * caption width. We remove the previous set width so the width is - * allowed to shrink. All widths are measured as outer sizes, i.e. the - * borderWidth is added to the content. - */ - - DOM.setStyleAttribute(getElement(), "width", ""); - - String oldHeaderWidth = ""; // Only for IE6 - if (BrowserInfo.get().isIE6()) { - /* - * For some reason IE6 has title DIV set to width 100% which - * interferes with the header measuring. Also IE6 has width set to - * the contentPanel. - */ - oldHeaderWidth = headerText.getStyle().getProperty("width"); - DOM.setStyleAttribute(contentPanel.getElement(), "width", "auto"); - DOM.setStyleAttribute(contentPanel.getElement(), "zoom", "1"); - headerText.getStyle().setProperty("width", "auto"); - } - - // Content - int contentWidth = contentPanel.getElement().getScrollWidth(); - contentWidth += getContentAreaToRootDifference(); - - // Window width (caption) - int windowCaptionWidth = getOffsetWidth(); - - int naturalWidth = (contentWidth > windowCaptionWidth ? contentWidth - : windowCaptionWidth); - - if (BrowserInfo.get().isIE6()) { - headerText.getStyle().setProperty("width", oldHeaderWidth); - } - - setWidth(naturalWidth + "px"); - } - - private int getContentAreaToRootDifference() { - if (contentAreaToRootDifference < 0) { - measure(); - } - return contentAreaToRootDifference; - } - - private void measure() { - if (!isAttached()) { - return; - } - - contentAreaBorderPadding = Util.measureHorizontalPaddingAndBorder( - contents, 4); - int wrapperPaddingBorder = Util.measureHorizontalPaddingAndBorder( - wrapper, 0) - + Util.measureHorizontalPaddingAndBorder(wrapper2, 0); - - contentAreaToRootDifference = wrapperPaddingBorder - + contentAreaBorderPadding; - - } - /** * Sets the closable state of the window. Additionally hides/shows the close * button according to the new state. @@ -697,42 +380,6 @@ public class VWindow extends VOverlay implements Container, showModalityCurtain(); } super.show(); - - setFF2CaretFixEnabled(true); - fixFF3OverflowBug(); - } - - /** Disable overflow auto with FF3 to fix #1837. */ - private void fixFF3OverflowBug() { - if (BrowserInfo.get().isFF3()) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - DOM.setStyleAttribute(getElement(), "overflow", ""); - } - }); - } - } - - /** - * Fix "missing cursor" browser bug workaround for FF2 in Windows and Linux. - * - * Calling this method has no effect on other browsers than the ones based - * on Gecko 1.8 - * - * @param enable - */ - private void setFF2CaretFixEnabled(boolean enable) { - if (BrowserInfo.get().isFF2()) { - if (enable) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - DOM.setStyleAttribute(getElement(), "overflow", "auto"); - } - }); - } else { - DOM.setStyleAttribute(getElement(), "overflow", ""); - } - } } @Override @@ -747,7 +394,7 @@ public class VWindow extends VOverlay implements Container, windowOrder.remove(this); } - private void setVaadinModality(boolean modality) { + void setVaadinModality(boolean modality) { vaadinModality = modality; if (vaadinModality) { if (isAttached()) { @@ -765,14 +412,6 @@ public class VWindow extends VOverlay implements Container, } private void showModalityCurtain() { - if (BrowserInfo.get().isFF2()) { - DOM.setStyleAttribute( - getModalityCurtain(), - "height", - DOM.getElementPropertyInt(RootPanel.getBodyElement(), - "offsetHeight") + "px"); - DOM.setStyleAttribute(getModalityCurtain(), "position", "absolute"); - } DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + (windowOrder.indexOf(this) + Z_INDEX)); if (isShowing()) { @@ -792,15 +431,11 @@ public class VWindow extends VOverlay implements Container, * iframes (etc) do not steal event. */ private void showDraggingCurtain() { - setFF2CaretFixEnabled(false); // makes FF2 slow - DOM.appendChild(RootPanel.getBodyElement(), getDraggingCurtain()); } private void hideDraggingCurtain() { if (draggingCurtain != null) { - setFF2CaretFixEnabled(true); // makes FF2 slow - DOM.removeChild(RootPanel.getBodyElement(), draggingCurtain); } } @@ -810,15 +445,11 @@ public class VWindow extends VOverlay implements Container, * that iframes (etc) do not steal event. */ private void showResizingCurtain() { - setFF2CaretFixEnabled(false); // makes FF2 slow - DOM.appendChild(RootPanel.getBodyElement(), getResizingCurtain()); } private void hideResizingCurtain() { if (resizingCurtain != null) { - setFF2CaretFixEnabled(true); // makes FF2 slow - DOM.removeChild(RootPanel.getBodyElement(), resizingCurtain); } } @@ -854,7 +485,7 @@ public class VWindow extends VOverlay implements Container, return curtain; } - private void setResizable(boolean resizability) { + void setResizable(boolean resizability) { resizable = resizability; if (resizability) { DOM.setElementProperty(footer, "className", CLASSNAME + "-footer"); @@ -1044,13 +675,15 @@ public class VWindow extends VOverlay implements Container, } int w = Util.getTouchOrMouseClientX(event) - startX + origW; - if (w < MIN_CONTENT_AREA_WIDTH + getContentAreaToRootDifference()) { - w = MIN_CONTENT_AREA_WIDTH + getContentAreaToRootDifference(); + int minWidth = getMinWidth(); + if (w < minWidth) { + w = minWidth; } int h = Util.getTouchOrMouseClientY(event) - startY + origH; - if (h < MIN_CONTENT_AREA_HEIGHT + getExtraHeight()) { - h = MIN_CONTENT_AREA_HEIGHT + getExtraHeight(); + int minHeight = getMinHeight(); + if (h < minHeight) { + h = minHeight; } setWidth(w + "px"); @@ -1074,121 +707,30 @@ public class VWindow extends VOverlay implements Container, private void updateContentsSize() { // Update child widget dimensions if (client != null) { - client.handleComponentRelativeSize((Widget) layout); - client.runDescendentsLayout((HasWidgets) layout); + client.handleComponentRelativeSize(layout.getWidget()); + client.runDescendentsLayout((HasWidgets) layout.getWidget()); } - Util.runWebkitOverflowAutoFix(contentPanel.getElement()); + LayoutManager layoutManager = LayoutManager.get(client); + layoutManager.setNeedsMeasure(ConnectorMap.get(client).getConnector( + this)); + layoutManager.layoutNow(); } @Override - /** - * Width is set to the out-most element (v-window). - * - * This function should never be called with percentage values (it will - * throw an exception) - */ public void setWidth(String width) { - this.width = width; - if (!isAttached()) { - return; - } - if (width != null && !"".equals(width)) { - int rootPixelWidth = -1; - if (width.indexOf("px") < 0) { - /* - * Convert non-pixel values to pixels by setting the width and - * then measuring it. Updates the "width" variable with the - * pixel width. - */ - DOM.setStyleAttribute(getElement(), "width", width); - rootPixelWidth = getElement().getOffsetWidth(); - width = rootPixelWidth + "px"; - } else { - rootPixelWidth = Integer.parseInt(width.substring(0, - width.indexOf("px"))); - } - - // "width" now contains the new width in pixels - - if (BrowserInfo.get().isIE6()) { - getElement().getStyle().setProperty("overflow", "hidden"); - } - - // Apply the new pixel width - getElement().getStyle().setProperty("width", width); - - // Caculate the inner width of the content area - int contentAreaInnerWidth = rootPixelWidth - - getContentAreaToRootDifference(); - if (contentAreaInnerWidth < MIN_CONTENT_AREA_WIDTH) { - contentAreaInnerWidth = MIN_CONTENT_AREA_WIDTH; - int rootWidth = contentAreaInnerWidth - + getContentAreaToRootDifference(); - DOM.setStyleAttribute(getElement(), "width", rootWidth + "px"); - } - - // IE6 needs the actual inner content width on the content element, - // otherwise it won't wrap the content properly (no scrollbars - // appear, content flows out of window) - if (BrowserInfo.get().isIE6()) { - DOM.setStyleAttribute(contentPanel.getElement(), "width", - contentAreaInnerWidth + "px"); - } - - renderSpace.setWidth(contentAreaInnerWidth); - - updateShadowSizeAndPosition(); - } + // Override PopupPanel which sets the width to the contents + getElement().getStyle().setProperty("width", width); + // Update v-has-width in case undefined window is resized + setStyleName("v-has-width", width != null && width.length() > 0); } @Override - /** - * Height is set to the out-most element (v-window). - * - * This function should never be called with percentage values (it will - * throw an exception) - * - * @param height A CSS string specifying the new height of the window. - * An empty string or null clears the height and lets - * the browser to compute it based on the window contents. - */ public void setHeight(String height) { - if (!isAttached() - || (height == null ? this.height == null : height - .equals(this.height))) { - return; - } - if (height == null || "".equals(height)) { - getElement().getStyle().clearHeight(); - contentPanel.getElement().getStyle().clearHeight(); - // Reset to default, the exact value does not actually - // matter as an undefined-height parent should not have - // a relative-height child anyway. - renderSpace.setHeight(MIN_CONTENT_AREA_HEIGHT); - } else { - getElement().getStyle().setProperty("height", height); - int contentHeight = getElement().getOffsetHeight() - - getExtraHeight(); - if (contentHeight < MIN_CONTENT_AREA_HEIGHT) { - contentHeight = MIN_CONTENT_AREA_HEIGHT; - int rootHeight = contentHeight + getExtraHeight(); - getElement().getStyle() - .setProperty("height", rootHeight + "px"); - } - renderSpace.setHeight(contentHeight); - contentPanel.getElement().getStyle() - .setProperty("height", contentHeight + "px"); - } - this.height = height; - updateShadowSizeAndPosition(); - } - - private int extraH = 0; - - private int getExtraHeight() { - extraH = header.getOffsetHeight() + footer.getOffsetHeight(); - return extraH; + // Override PopupPanel which sets the height to the contents + getElement().getStyle().setProperty("height", height); + // Update v-has-height in case undefined window is resized + setStyleName("v-has-height", height != null && height.length() > 0); } private void onDragEvent(Event event) { @@ -1264,22 +806,34 @@ public class VWindow extends VOverlay implements Container, } else if (resizing) { onResizeEvent(event); return false; - } else if (vaadinModality) { - // return false when modal and outside window - final Element target = event.getEventTarget().cast(); + } + + // TODO This is probably completely unnecessary as the modality curtain + // prevents events from reaching other windows and any security check + // must be done on the server side and not here. + // The code here is also run many times as each VWindow has an event + // preview but we cannot check only the current VWindow here (e.g. + // if(isTopMost) {...}) because PopupPanel will cause all events that + // are not cancelled here and target this window to be consume():d + // meaning the event won't be sent to the rest of the preview handlers. + + if (getTopmostWindow().vaadinModality) { + // Topmost window is modal. Cancel the event if it targets something + // outside that window (except debug console...) if (DOM.getCaptureElement() != null) { // Allow events when capture is set return true; } - if (!DOM.isOrHasChild(getElement(), target)) { + final Element target = event.getEventTarget().cast(); + if (!DOM.isOrHasChild(getTopmostWindow().getElement(), target)) { // not within the modal window, but let's see if it's in the // debug window Widget w = Util.findWidget(target, null); while (w != null) { if (w instanceof Console) { return true; // allow debug-window clicks - } else if (w instanceof Paintable) { + } else if (ConnectorMap.get(client).isConnector(w)) { return false; } w = w.getParent(); @@ -1298,52 +852,6 @@ public class VWindow extends VOverlay implements Container, true); } - @Override - protected void onAttach() { - super.onAttach(); - - setWidth(width); - setHeight(height); - } - - public RenderSpace getAllocatedSpace(Widget child) { - if (child == layout) { - return renderSpace; - } else { - // Exception ?? - return null; - } - } - - public boolean hasChildComponent(Widget component) { - if (component == layout) { - return true; - } else { - return false; - } - } - - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - contentPanel.setWidget(newComponent); - } - - public boolean requestLayout(Set<Paintable> child) { - if (dynamicWidth && !layoutRelativeWidth) { - setNaturalWidth(); - } - if (centered) { - center(); - } - updateShadowSizeAndPosition(); - // layout size change may affect its available space (scrollbars) - client.handleComponentRelativeSize((Widget) layout); - return true; - } - - public void updateCaption(Paintable component, UIDL uidl) { - // NOP, window has own caption, layout captio not rendered - } - public ShortcutActionHandler getShortcutActionHandler() { return shortcutHandler; } @@ -1376,13 +884,29 @@ public class VWindow extends VOverlay implements Container, } } - public void onBeforeShortcutAction(Event e) { - // NOP, nothing to update just avoid workaround ( causes excess - // blur/focus ) - } - public void focus() { contentPanel.focus(); } + public int getMinHeight() { + return MIN_CONTENT_AREA_HEIGHT + getDecorationHeight(); + } + + private int getDecorationHeight() { + LayoutManager lm = layout.getLayoutManager(); + int headerHeight = lm.getOuterHeight(header); + int footerHeight = lm.getOuterHeight(footer); + return headerHeight + footerHeight; + } + + public int getMinWidth() { + return MIN_CONTENT_AREA_WIDTH + getDecorationWidth(); + } + + private int getDecorationWidth() { + LayoutManager layoutManager = layout.getLayoutManager(); + return layoutManager.getOuterWidth(getElement()) + - contentPanel.getElement().getOffsetWidth(); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java b/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java new file mode 100644 index 0000000000..6979982a9c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java @@ -0,0 +1,305 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.window; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.layout.MayScrollChildren; + +@Connect(value = com.vaadin.ui.Window.class) +public class WindowConnector extends AbstractComponentContainerConnector + implements Paintable, BeforeShortcutActionListener, + SimpleManagedLayout, PostLayoutListener, MayScrollChildren { + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.click(mouseDetails); + } + }; + + private WindowServerRpc rpc; + + boolean minWidthChecked = false; + + @Override + public boolean delegateCaptionHandling() { + return false; + }; + + @Override + protected void init() { + super.init(); + rpc = RpcProxy.create(WindowServerRpc.class, this); + + getLayoutManager().registerDependency(this, + getWidget().contentPanel.getElement()); + getLayoutManager().registerDependency(this, getWidget().header); + getLayoutManager().registerDependency(this, getWidget().footer); + } + + @Override + public void onUnregister() { + LayoutManager lm = getLayoutManager(); + VWindow window = getWidget(); + lm.unregisterDependency(this, window.contentPanel.getElement()); + lm.unregisterDependency(this, window.header); + lm.unregisterDependency(this, window.footer); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().id = getConnectorId(); + getWidget().client = client; + + // Workaround needed for Testing Tools (GWT generates window DOM + // slightly different in different browsers). + DOM.setElementProperty(getWidget().closeBox, "id", getConnectorId() + + "_window_close"); + + if (isRealUpdate(uidl)) { + if (getState().isModal() != getWidget().vaadinModality) { + getWidget().setVaadinModality(!getWidget().vaadinModality); + } + if (!getWidget().isAttached()) { + getWidget().setVisible(false); // hide until + // possible centering + getWidget().show(); + } + if (getState().isResizable() != getWidget().resizable) { + getWidget().setResizable(getState().isResizable()); + } + getWidget().resizeLazy = getState().isResizeLazy(); + + getWidget().setDraggable(getState().isDraggable()); + + // Caption must be set before required header size is measured. If + // the caption attribute is missing the caption should be cleared. + String iconURL = null; + if (getState().getIcon() != null) { + iconURL = getState().getIcon().getURL(); + } + getWidget().setCaption(getState().getCaption(), iconURL); + } + + getWidget().visibilityChangesDisabled = true; + if (!isRealUpdate(uidl)) { + return; + } + getWidget().visibilityChangesDisabled = false; + + clickEventHandler.handleEventHandlerRegistration(); + + getWidget().immediate = getState().isImmediate(); + + getWidget().setClosable(!isReadOnly()); + + // Initialize the position form UIDL + int positionx = getState().getPositionX(); + int positiony = getState().getPositionY(); + if (positionx >= 0 || positiony >= 0) { + if (positionx < 0) { + positionx = 0; + } + if (positiony < 0) { + positiony = 0; + } + getWidget().setPopupPosition(positionx, positiony); + } + + int childIndex = 0; + + // we may have actions + for (int i = 0; i < uidl.getChildCount(); i++) { + UIDL childUidl = uidl.getChildUIDL(i); + if (childUidl.getTag().equals("actions")) { + if (getWidget().shortcutHandler == null) { + getWidget().shortcutHandler = new ShortcutActionHandler( + getConnectorId(), client); + } + getWidget().shortcutHandler.updateActionMap(childUidl); + } + + } + + // setting scrollposition must happen after children is rendered + getWidget().contentPanel.setScrollPosition(getState().getScrollTop()); + getWidget().contentPanel.setHorizontalScrollPosition(getState() + .getScrollLeft()); + + // Center this window on screen if requested + // This had to be here because we might not know the content size before + // everything is painted into the window + + // centered is this is unset on move/resize + getWidget().centered = getState().isCentered(); + getWidget().setVisible(true); + + // ensure window is not larger than browser window + if (getWidget().getOffsetWidth() > Window.getClientWidth()) { + getWidget().setWidth(Window.getClientWidth() + "px"); + } + if (getWidget().getOffsetHeight() > Window.getClientHeight()) { + getWidget().setHeight(Window.getClientHeight() + "px"); + } + + if (uidl.hasAttribute("bringToFront")) { + /* + * Focus as a side-effect. Will be overridden by + * ApplicationConnection if another component was focused by the + * server side. + */ + getWidget().contentPanel.focus(); + getWidget().bringToFrontSequence = uidl + .getIntAttribute("bringToFront"); + VWindow.deferOrdering(); + } + } + + public void updateCaption(ComponentConnector component) { + // NOP, window has own caption, layout caption not rendered + } + + public void onBeforeShortcutAction(Event e) { + // NOP, nothing to update just avoid workaround ( causes excess + // blur/focus ) + } + + @Override + public VWindow getWidget() { + return (VWindow) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VWindow.class); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + // We always have 1 child, unless the child is hidden + Widget newChildWidget = null; + ComponentConnector newChild = null; + if (getChildren().size() == 1) { + newChild = getChildren().get(0); + newChildWidget = newChild.getWidget(); + } + + getWidget().layout = newChild; + getWidget().contentPanel.setWidget(newChildWidget); + } + + public void layout() { + LayoutManager lm = getLayoutManager(); + VWindow window = getWidget(); + ComponentConnector layout = window.layout; + Element contentElement = window.contentPanel.getElement(); + + if (!minWidthChecked) { + boolean needsMinWidth = !isUndefinedWidth() + || layout.isRelativeWidth(); + int minWidth = window.getMinWidth(); + if (needsMinWidth && lm.getInnerWidth(contentElement) < minWidth) { + minWidthChecked = true; + // Use minimum width if less than a certain size + window.setWidth(minWidth + "px"); + } + minWidthChecked = true; + } + + boolean needsMinHeight = !isUndefinedHeight() + || layout.isRelativeHeight(); + int minHeight = window.getMinHeight(); + if (needsMinHeight && lm.getInnerHeight(contentElement) < minHeight) { + // Use minimum height if less than a certain size + window.setHeight(minHeight + "px"); + } + + Style contentStyle = window.contents.getStyle(); + + int headerHeight = lm.getOuterHeight(window.header); + contentStyle.setPaddingTop(headerHeight, Unit.PX); + contentStyle.setMarginTop(-headerHeight, Unit.PX); + + int footerHeight = lm.getOuterHeight(window.footer); + contentStyle.setPaddingBottom(footerHeight, Unit.PX); + contentStyle.setMarginBottom(-footerHeight, Unit.PX); + + /* + * Must set absolute position if the child has relative height and + * there's a chance of horizontal scrolling as some browsers will + * otherwise not take the scrollbar into account when calculating the + * height. + */ + Element layoutElement = layout.getWidget().getElement(); + Style childStyle = layoutElement.getStyle(); + if (layout.isRelativeHeight() && !BrowserInfo.get().isIE9()) { + childStyle.setPosition(Position.ABSOLUTE); + + Style wrapperStyle = contentElement.getStyle(); + if (window.getElement().getStyle().getWidth().length() == 0 + && !layout.isRelativeWidth()) { + /* + * Need to lock width to make undefined width work even with + * absolute positioning + */ + int contentWidth = lm.getOuterWidth(layoutElement); + wrapperStyle.setWidth(contentWidth, Unit.PX); + } else { + wrapperStyle.clearWidth(); + } + } else { + childStyle.clearPosition(); + } + } + + public void postLayout() { + minWidthChecked = false; + VWindow window = getWidget(); + if (window.centered) { + window.center(); + } + window.updateShadowSizeAndPosition(); + } + + @Override + public WindowState getState() { + return (WindowState) super.getState(); + } + + /** + * Gives the WindowConnector an order number. As a side effect, moves the + * window according to its order number so the windows are stacked. This + * method should be called for each window in the order they should appear. + */ + public void setWindowOrderAndPosition() { + getWidget().setWindowOrderAndPosition(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/WindowServerRpc.java b/src/com/vaadin/terminal/gwt/client/ui/window/WindowServerRpc.java new file mode 100644 index 0000000000..4723c55786 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/window/WindowServerRpc.java @@ -0,0 +1,10 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.window; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; + +public interface WindowServerRpc extends ClickRpc, ServerRpc { +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/WindowState.java b/src/com/vaadin/terminal/gwt/client/ui/window/WindowState.java new file mode 100644 index 0000000000..b057d76b16 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/window/WindowState.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.window; + +import com.vaadin.terminal.gwt.client.ui.panel.PanelState; + +public class WindowState extends PanelState { + private boolean modal = false; + private boolean resizable = true; + private boolean resizeLazy = false; + private boolean draggable = true; + private boolean centered = false;; + private int positionX = -1; + private int positionY = -1; + + public boolean isModal() { + return modal; + } + + public void setModal(boolean modal) { + this.modal = modal; + } + + public boolean isResizable() { + return resizable; + } + + public void setResizable(boolean resizable) { + this.resizable = resizable; + } + + public boolean isResizeLazy() { + return resizeLazy; + } + + public void setResizeLazy(boolean resizeLazy) { + this.resizeLazy = resizeLazy; + } + + public boolean isDraggable() { + return draggable; + } + + public void setDraggable(boolean draggable) { + this.draggable = draggable; + } + + public boolean isCentered() { + return centered; + } + + public void setCentered(boolean centered) { + this.centered = centered; + } + + public int getPositionX() { + return positionX; + } + + public void setPositionX(int positionX) { + this.positionX = positionX; + } + + public int getPositionY() { + return positionY; + } + + public void setPositionY(int positionY) { + this.positionY = positionY; + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index a5923cb47f..58d6a18592 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -14,14 +14,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.security.GeneralSecurityException; -import java.util.Date; import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; -import java.util.logging.Level; import java.util.logging.Logger; import javax.portlet.ActionRequest; @@ -30,19 +26,16 @@ import javax.portlet.EventRequest; import javax.portlet.EventResponse; import javax.portlet.GenericPortlet; import javax.portlet.MimeResponse; -import javax.portlet.PortalContext; import javax.portlet.PortletConfig; import javax.portlet.PortletContext; import javax.portlet.PortletException; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; import javax.portlet.PortletSession; -import javax.portlet.PortletURL; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; -import javax.portlet.ResourceURL; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; @@ -50,12 +43,15 @@ import javax.servlet.http.HttpServletResponse; import com.liferay.portal.kernel.util.PortalClassInvoker; import com.liferay.portal.kernel.util.PropsUtil; import com.vaadin.Application; +import com.vaadin.Application.ApplicationStartEvent; import com.vaadin.Application.SystemMessages; -import com.vaadin.terminal.DownloadStream; +import com.vaadin.RootRequiresMoreInformationException; +import com.vaadin.terminal.DeploymentConfiguration; import com.vaadin.terminal.Terminal; -import com.vaadin.terminal.gwt.client.ApplicationConfiguration; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.ui.Window; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; +import com.vaadin.terminal.gwt.server.AbstractCommunicationManager.Callback; +import com.vaadin.ui.Root; /** * Portlet 2.0 base class. This replaces the servlet in servlet/portlet 1.0 @@ -71,46 +67,256 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet private static final Logger logger = Logger .getLogger(AbstractApplicationPortlet.class.getName()); + private static class WrappedHttpAndPortletRequest extends + WrappedPortletRequest { + + public WrappedHttpAndPortletRequest(PortletRequest request, + HttpServletRequest originalRequest, + DeploymentConfiguration deploymentConfiguration) { + super(request, deploymentConfiguration); + this.originalRequest = originalRequest; + } + + private final HttpServletRequest originalRequest; + + @Override + public String getParameter(String name) { + String parameter = super.getParameter(name); + if (parameter == null) { + parameter = originalRequest.getParameter(name); + } + return parameter; + } + + @Override + public String getRemoteAddr() { + return originalRequest.getRemoteAddr(); + } + + @Override + public String getHeader(String name) { + String header = super.getHeader(name); + if (header == null) { + header = originalRequest.getHeader(name); + } + return header; + } + + @Override + public Map<String, String[]> getParameterMap() { + Map<String, String[]> parameterMap = super.getParameterMap(); + if (parameterMap == null) { + parameterMap = originalRequest.getParameterMap(); + } + return parameterMap; + } + } + + private static class WrappedGateinRequest extends + WrappedHttpAndPortletRequest { + public WrappedGateinRequest(PortletRequest request, + DeploymentConfiguration deploymentConfiguration) { + super(request, getOriginalRequest(request), deploymentConfiguration); + } + + private static final HttpServletRequest getOriginalRequest( + PortletRequest request) { + try { + Method getRealReq = request.getClass().getMethod( + "getRealRequest"); + HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq + .invoke(request); + return origRequest; + } catch (Exception e) { + throw new IllegalStateException("GateIn request not detected", + e); + } + } + } + + private static class WrappedLiferayRequest extends + WrappedHttpAndPortletRequest { + + public WrappedLiferayRequest(PortletRequest request, + DeploymentConfiguration deploymentConfiguration) { + super(request, getOriginalRequest(request), deploymentConfiguration); + } + + @Override + public String getPortalProperty(String name) { + return PropsUtil.get(name); + } + + private static HttpServletRequest getOriginalRequest( + PortletRequest request) { + try { + // httpRequest = PortalUtil.getHttpServletRequest(request); + HttpServletRequest httpRequest = (HttpServletRequest) PortalClassInvoker + .invoke("com.liferay.portal.util.PortalUtil", + "getHttpServletRequest", request); + + // httpRequest = + // PortalUtil.getOriginalServletRequest(httpRequest); + httpRequest = (HttpServletRequest) PortalClassInvoker.invoke( + "com.liferay.portal.util.PortalUtil", + "getOriginalServletRequest", httpRequest); + return httpRequest; + } catch (Exception e) { + throw new IllegalStateException("Liferay request not detected", + e); + } + } + + } + + private static class AbstractApplicationPortletWrapper implements Callback { + + private final AbstractApplicationPortlet portlet; + + public AbstractApplicationPortletWrapper( + AbstractApplicationPortlet portlet) { + this.portlet = portlet; + } + + public void criticalNotification(WrappedRequest request, + WrappedResponse response, String cap, String msg, + String details, String outOfSyncURL) throws IOException { + PortletRequest portletRequest = WrappedPortletRequest.cast(request) + .getPortletRequest(); + PortletResponse portletResponse = ((WrappedPortletResponse) response) + .getPortletResponse(); + portlet.criticalNotification(portletRequest, + (MimeResponse) portletResponse, cap, msg, details, + outOfSyncURL); + } + } + /** * This portlet parameter is used to add styles to the main element. E.g * "height:500px" generates a style="height:500px" to the main element. */ public static final String PORTLET_PARAMETER_STYLE = "style"; - private static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme"; + /** + * This portal parameter is used to define the name of the Vaadin theme that + * is used for all Vaadin applications in the portal. + */ + public static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme"; // TODO some parts could be shared with AbstractApplicationServlet // TODO Can we close the application when the portlet is removed? Do we know // when the portlet is removed? - // TODO What happens when the portlet window is resized? Do we know when the - // window is resized? - private Properties applicationProperties; private boolean productionMode = false; + private DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration() { + public String getConfiguredWidgetset(WrappedRequest request) { + + String widgetset = getApplicationOrSystemProperty( + PARAMETER_WIDGETSET, null); + + if (widgetset == null) { + // If no widgetset defined for the application, check the portal + // property + widgetset = WrappedPortletRequest.cast(request) + .getPortalProperty(PORTAL_PARAMETER_VAADIN_WIDGETSET); + } + + if (widgetset == null) { + // If no widgetset defined for the portal, use the default + widgetset = DEFAULT_WIDGETSET; + } + + return widgetset; + } + + public String getConfiguredTheme(WrappedRequest request) { + + // is the default theme defined by the portal? + String themeName = WrappedPortletRequest.cast(request) + .getPortalProperty(Constants.PORTAL_PARAMETER_VAADIN_THEME); + + if (themeName == null) { + // no, using the default theme defined by Vaadin + themeName = DEFAULT_THEME_NAME; + } + + return themeName; + } + + public String getApplicationOrSystemProperty(String propertyName, + String defaultValue) { + return AbstractApplicationPortlet.this + .getApplicationOrSystemProperty(propertyName, defaultValue); + } + + public boolean isStandalone(WrappedRequest request) { + return false; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.DeploymentConfiguration#getStaticFileLocation + * (com.vaadin.terminal.WrappedRequest) + * + * Return the URL from where static files, e.g. the widgetset and the + * theme, are served. In a standard configuration the VAADIN folder + * inside the returned folder is what is used for widgetsets and themes. + * + * @return The location of static resources (inside which there should + * be a VAADIN directory). Does not end with a slash (/). + */ + public String getStaticFileLocation(WrappedRequest request) { + String staticFileLocation = WrappedPortletRequest.cast(request) + .getPortalProperty( + Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH); + if (staticFileLocation != null) { + // remove trailing slash if any + while (staticFileLocation.endsWith(".")) { + staticFileLocation = staticFileLocation.substring(0, + staticFileLocation.length() - 1); + } + return staticFileLocation; + } else { + // default for Liferay + return "/html"; + } + } + + public ClassLoader getClassLoader() { + // Custom class loaders not currently supported in portlets (see + // #8574) + return null; + } + }; + @Override public void init(PortletConfig config) throws PortletException { super.init(config); - // Stores the application parameters into Properties object applicationProperties = new Properties(); - for (final Enumeration<String> e = config.getInitParameterNames(); e + + // Read default parameters from the context + final PortletContext context = config.getPortletContext(); + for (final Enumeration<String> e = context.getInitParameterNames(); e .hasMoreElements();) { final String name = e.nextElement(); applicationProperties.setProperty(name, - config.getInitParameter(name)); + context.getInitParameter(name)); } - // Overrides with server.xml parameters - final PortletContext context = config.getPortletContext(); - for (final Enumeration<String> e = context.getInitParameterNames(); e + // Override with application settings from portlet.xml + for (final Enumeration<String> e = config.getInitParameterNames(); e .hasMoreElements();) { final String name = e.nextElement(); applicationProperties.setProperty(name, - context.getInitParameter(name)); + config.getInitParameter(name)); } + checkProductionMode(); checkCrossSiteProtection(); } @@ -133,25 +339,21 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * * @param request */ - private void checkWidgetsetVersion(PortletRequest request) { - if (!AbstractApplicationServlet.VERSION.equals(getHTTPRequestParameter( - request, "wsver"))) { + private void checkWidgetsetVersion(WrappedRequest request) { + if (!AbstractApplicationServlet.VERSION.equals(request + .getParameter("wsver"))) { logger.warning(String.format(WIDGETSET_MISMATCH_INFO, AbstractApplicationServlet.VERSION, - getHTTPRequestParameter(request, "wsver"))); + request.getParameter("wsver"))); } } private void checkProductionMode() { + // TODO Identical code in AbstractApplicationServlet -> refactor // Check if the application is in production mode. - // We are in production mode if Debug=false or productionMode=true - if (getApplicationOrSystemProperty(SERVLET_PARAMETER_DEBUG, "true") - .equals("false")) { - // "Debug=true" is the old way and should no longer be used - productionMode = true; - } else if (getApplicationOrSystemProperty( - SERVLET_PARAMETER_PRODUCTION_MODE, "false").equals("true")) { - // "productionMode=true" is the real way to do it + // We are in production mode if productionMode=true + if (getApplicationOrSystemProperty(SERVLET_PARAMETER_PRODUCTION_MODE, + "false").equals("true")) { productionMode = true; } @@ -241,48 +443,24 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet return defaultValue; } - /** - * Return the URL from where static files, e.g. the widgetset and the theme, - * are served. In a standard configuration the VAADIN folder inside the - * returned folder is what is used for widgetsets and themes. - * - * @param request - * @return The location of static resources (inside which there should be a - * VAADIN directory). Does not end with a slash (/). - */ - protected String getStaticFilesLocation(PortletRequest request) { - // TODO allow overriding on portlet level? - String staticFileLocation = getPortalProperty( - Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH, - request.getPortalContext()); - if (staticFileLocation != null) { - // remove trailing slash if any - while (staticFileLocation.endsWith(".")) { - staticFileLocation = staticFileLocation.substring(0, - staticFileLocation.length() - 1); - } - return staticFileLocation; - } else { - // default for Liferay - return "/html"; - } - } - protected enum RequestType { - FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN; + FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS; } protected RequestType getRequestType(PortletRequest request) { if (request instanceof RenderRequest) { return RequestType.RENDER; } else if (request instanceof ResourceRequest) { - if (isUIDLRequest((ResourceRequest) request)) { + ResourceRequest resourceRequest = (ResourceRequest) request; + if (isUIDLRequest(resourceRequest)) { return RequestType.UIDL; - } else if (isFileUploadRequest((ResourceRequest) request)) { + } else if (isBrowserDetailsRequeset(resourceRequest)) { + return RequestType.BROWSER_DETAILS; + } else if (isFileUploadRequest(resourceRequest)) { return RequestType.FILE_UPLOAD; - } else if (isApplicationResourceRequest((ResourceRequest) request)) { + } else if (isApplicationResourceRequest(resourceRequest)) { return RequestType.APPLICATION_RESOURCE; - } else if (isDummyRequest((ResourceRequest) request)) { + } else if (isDummyRequest(resourceRequest)) { return RequestType.DUMMY; } else { return RequestType.STATIC_FILE; @@ -295,6 +473,11 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet return RequestType.UNKNOWN; } + private boolean isBrowserDetailsRequeset(ResourceRequest request) { + return request.getResourceID() != null + && request.getResourceID().equals("browserDetails"); + } + private boolean isApplicationResourceRequest(ResourceRequest request) { return request.getResourceID() != null && request.getResourceID().startsWith("APP"); @@ -326,8 +509,27 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet protected void handleRequest(PortletRequest request, PortletResponse response) throws PortletException, IOException { - RequestTimer.RequestWrapper wrappedRequest = new RequestTimer.RequestWrapper( - request); + AbstractApplicationPortletWrapper portletWrapper = new AbstractApplicationPortletWrapper( + this); + + WrappedPortletRequest wrappedRequest; + + String portalInfo = request.getPortalContext().getPortalInfo() + .toLowerCase(); + if (portalInfo.contains("liferay")) { + wrappedRequest = new WrappedLiferayRequest(request, + getDeploymentConfiguration()); + } else if (portalInfo.contains("gatein")) { + wrappedRequest = new WrappedGateinRequest(request, + getDeploymentConfiguration()); + } else { + wrappedRequest = new WrappedPortletRequest(request, + getDeploymentConfiguration()); + } + + WrappedPortletResponse wrappedResponse = new WrappedPortletResponse( + response, getDeploymentConfiguration()); + RequestTimer requestTimer = RequestTimer.get(wrappedRequest); requestTimer.start(wrappedRequest); @@ -360,10 +562,12 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet // TODO What about PARAM_UNLOADBURST & redirectToApplication?? /* Find out which application this request is related to */ - application = findApplicationInstance(request, requestType); + application = findApplicationInstance(wrappedRequest, + requestType); if (application == null) { return; } + Application.setCurrentApplication(application); /* * Get or create an application context and an application @@ -378,8 +582,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet .getApplicationManager(application); /* Update browser information from request */ - updateBrowserProperties(applicationContext.getBrowser(), - request); + applicationContext.getBrowser().updateRequestDetails( + wrappedRequest); /* * Call application requestStart before Application.init() is @@ -404,20 +608,36 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet /* Notify listeners */ // Finds the window within the application - Window window = null; + Root root = null; synchronized (application) { if (application.isRunning()) { switch (requestType) { + case RENDER: + case ACTION: + // Both action requests and render requests are ok + // without a Root as they render the initial HTML + // and then do a second request + try { + root = application + .getRootForRequest(wrappedRequest); + } catch (RootRequiresMoreInformationException e) { + // Ignore problem and continue without root + } + break; + case BROWSER_DETAILS: + // Should not try to find a root here as the + // combined request details might change the root + break; case FILE_UPLOAD: // no window break; case APPLICATION_RESOURCE: // use main window - should not need any window - window = application.getMainWindow(); + // root = application.getRoot(); break; default: - window = applicationManager.getApplicationWindow( - request, this, application, null); + root = application + .getRootForRequest(wrappedRequest); } // if window not found, not a problem - use null } @@ -427,37 +647,39 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet // starts? if (request instanceof RenderRequest) { applicationContext.firePortletRenderRequest(application, - window, (RenderRequest) request, + root, (RenderRequest) request, (RenderResponse) response); } else if (request instanceof ActionRequest) { applicationContext.firePortletActionRequest(application, - window, (ActionRequest) request, + root, (ActionRequest) request, (ActionResponse) response); } else if (request instanceof EventRequest) { applicationContext.firePortletEventRequest(application, - window, (EventRequest) request, + root, (EventRequest) request, (EventResponse) response); } else if (request instanceof ResourceRequest) { applicationContext.firePortletResourceRequest(application, - window, (ResourceRequest) request, + root, (ResourceRequest) request, (ResourceResponse) response); } /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload( - (ResourceRequest) request, - (ResourceResponse) response); + applicationManager.handleFileUpload(wrappedRequest, + wrappedResponse); + return; + } else if (requestType == RequestType.BROWSER_DETAILS) { + applicationManager.handleBrowserDetailsRequest( + wrappedRequest, wrappedResponse, application); return; } else if (requestType == RequestType.UIDL) { // Handles AJAX UIDL requests if (isRepaintAll(request)) { // warn if versions do not match - checkWidgetsetVersion(request); + checkWidgetsetVersion(wrappedRequest); } - applicationManager.handleUidlRequest( - (ResourceRequest) request, - (ResourceResponse) response, this, window); + applicationManager.handleUidlRequest(wrappedRequest, + wrappedResponse, portletWrapper, root); return; } else { /* @@ -468,8 +690,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet return; } - handleOtherRequest(request, response, requestType, - application, window, applicationContext, + handleOtherRequest(wrappedRequest, wrappedResponse, + requestType, application, applicationContext, applicationManager); } } catch (final SessionExpiredException e) { @@ -490,10 +712,15 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet .endTransaction(application, request); } } finally { - if (requestStarted) { - ((PortletRequestListener) application).onRequestEnd( - request, response); + try { + if (requestStarted) { + ((PortletRequestListener) application) + .onRequestEnd(request, response); + } + } finally { + Root.setCurrentRoot(null); + Application.setCurrentApplication(null); } requestTimer.stop(); @@ -503,6 +730,10 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } } + private DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + private void handleUnknownRequest(PortletRequest request, PortletResponse response) { logger.warning("Unknown request type"); @@ -525,37 +756,18 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * @throws IOException * @throws MalformedURLException */ - private void handleOtherRequest(PortletRequest request, - PortletResponse response, RequestType requestType, - Application application, Window window, + private void handleOtherRequest(WrappedPortletRequest request, + WrappedResponse response, RequestType requestType, + Application application, PortletApplicationContext2 applicationContext, PortletCommunicationManager applicationManager) throws PortletException, IOException, MalformedURLException { - if (window == null) { - throw new PortletException(ERROR_NO_WINDOW_FOUND); - } - - /* - * Sets terminal type for the window, if not already set - */ - if (window.getTerminal() == null) { - window.setTerminal(applicationContext.getBrowser()); - } - - /* - * Handle parameters - */ - final Map<String, String[]> parameters = request.getParameterMap(); - if (window != null && parameters != null) { - window.handleParameters(parameters); - } - - if (requestType == RequestType.APPLICATION_RESOURCE) { - handleURI(applicationManager, window, (ResourceRequest) request, - (ResourceResponse) response); - } else if (requestType == RequestType.RENDER) { - writeAjaxPage((RenderRequest) request, (RenderResponse) response, - window, application); + if (requestType == RequestType.APPLICATION_RESOURCE + || requestType == RequestType.RENDER) { + if (!applicationManager.handleApplicationRequest(request, response)) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, + "Not found"); + } } else if (requestType == RequestType.EVENT) { // nothing to do, listeners do all the work } else if (requestType == RequestType.ACTION) { @@ -566,126 +778,12 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } } - private void updateBrowserProperties(WebBrowser browser, - PortletRequest request) { - String userAgent = getHTTPHeader(request, "user-agent"); - browser.updateRequestDetails(request.getLocale(), null, - request.isSecure(), userAgent); - if (getHTTPRequestParameter(request, "repaintAll") != null) { - browser.updateClientSideDetails( - getHTTPRequestParameter(request, "sw"), - getHTTPRequestParameter(request, "sh"), - getHTTPRequestParameter(request, "cw"), - getHTTPRequestParameter(request, "ch"), - getHTTPRequestParameter(request, "tzo"), - getHTTPRequestParameter(request, "rtzo"), - getHTTPRequestParameter(request, "dstd"), - getHTTPRequestParameter(request, "dstActive"), - getHTTPRequestParameter(request, "curdate"), - getHTTPRequestParameter(request, "td") != null); - } - } - @Override public void processEvent(EventRequest request, EventResponse response) throws PortletException, IOException { handleRequest(request, response); } - private boolean handleURI(PortletCommunicationManager applicationManager, - Window window, ResourceRequest request, ResourceResponse response) - throws IOException { - // Handles the URI - DownloadStream download = applicationManager.handleURI(window, request, - response, this); - - // A download request - if (download != null) { - // Client downloads an resource - handleDownload(download, request, response); - return true; - } - - return false; - } - - private void handleDownload(DownloadStream stream, ResourceRequest request, - ResourceResponse response) throws IOException { - - if (stream.getParameter("Location") != null) { - response.setProperty(ResourceResponse.HTTP_STATUS_CODE, - Integer.toString(HttpServletResponse.SC_MOVED_TEMPORARILY)); - response.setProperty("Location", stream.getParameter("Location")); - return; - } - - // Download from given stream - final InputStream data = stream.getStream(); - if (data != null) { - - OutputStream out = null; - try { - - // Sets content type - response.setContentType(stream.getContentType()); - - // Sets cache headers - final long cacheTime = stream.getCacheTime(); - if (cacheTime <= 0) { - response.setProperty("Cache-Control", "no-cache"); - response.setProperty("Pragma", "no-cache"); - response.setProperty("Expires", "0"); - } else { - response.setProperty("Cache-Control", "max-age=" - + cacheTime / 1000); - response.setProperty("Expires", - "" + System.currentTimeMillis() + cacheTime); - // Required to apply caching in some Tomcats - response.setProperty("Pragma", "cache"); - } - - // Copy download stream parameters directly - // to HTTP headers. - final Iterator<String> i = stream.getParameterNames(); - if (i != null) { - while (i.hasNext()) { - final String param = i.next(); - response.setProperty(param, stream.getParameter(param)); - } - } - - // suggest local filename from DownloadStream if - // Content-Disposition - // not explicitly set - String contentDispositionValue = stream - .getParameter("Content-Disposition"); - if (contentDispositionValue == null) { - contentDispositionValue = "filename=\"" - + stream.getFileName() + "\""; - response.setProperty("Content-Disposition", - contentDispositionValue); - } - - int bufferSize = stream.getBufferSize(); - if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) { - bufferSize = DEFAULT_BUFFER_SIZE; - } - final byte[] buffer = new byte[bufferSize]; - int bytesRead = 0; - - out = response.getPortletOutputStream(); - - while ((bytesRead = data.read(buffer)) > 0) { - out.write(buffer, 0, bytesRead); - out.flush(); - } - } finally { - AbstractCommunicationManager.tryToCloseStream(data); - AbstractCommunicationManager.tryToCloseStream(out); - } - } - } - private void serveStaticResources(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { final String resourceID = request.getResourceID(); @@ -774,7 +872,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet Locale locale = request.getLocale(); application.setLocale(locale); // No application URL when running inside a portlet - application.start(null, applicationProperties, context); + application.start(new ApplicationStartEvent(null, + applicationProperties, context, isProductionMode())); } } @@ -788,9 +887,11 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet // Do not send any redirects when running inside a portlet. } - private Application findApplicationInstance(PortletRequest request, - RequestType requestType) throws PortletException, - SessionExpiredException, MalformedURLException { + private Application findApplicationInstance( + WrappedPortletRequest wrappedRequest, RequestType requestType) + throws PortletException, SessionExpiredException, + MalformedURLException { + PortletRequest request = wrappedRequest.getPortletRequest(); boolean requestCanCreateApplication = requestCanCreateApplication( request, requestType); @@ -805,10 +906,10 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * user not specifically requested to close or restart it. */ - final boolean restartApplication = (getHTTPRequestParameter( - request, URL_PARAMETER_RESTART_APPLICATION) != null); - final boolean closeApplication = (getHTTPRequestParameter(request, - URL_PARAMETER_CLOSE_APPLICATION) != null); + final boolean restartApplication = (wrappedRequest + .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); + final boolean closeApplication = (wrappedRequest + .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); if (restartApplication) { closeApplication(application, request.getPortletSession(false)); @@ -878,436 +979,6 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet return null; } - /** - * Returns the URL from which the widgetset is served on the portal. - * - * @param widgetset - * @param request - * @return - */ - protected String getWidgetsetURL(String widgetset, PortletRequest request) { - return getStaticFilesLocation(request) + "/" + WIDGETSET_DIRECTORY_PATH - + widgetset + "/" + widgetset + ".nocache.js?" - + new Date().getTime(); - } - - /** - * Returns the theme URI for the named theme on the portal. - * - * Note that this is not the only location referring to the theme URI - also - * e.g. PortletCommunicationManager uses its own way to access the portlet - * 2.0 theme resources. - * - * @param themeName - * @param request - * @return - */ - protected String getThemeURI(String themeName, PortletRequest request) { - return getStaticFilesLocation(request) + "/" + THEME_DIRECTORY_PATH - + themeName; - } - - /** - * Writes the html host page (aka kickstart page) that starts the actual - * Vaadin application. - * - * If one needs to override parts of the portlet HTML contents creation, it - * is suggested that one overrides one of several submethods including: - * <ul> - * <li> - * {@link #writeAjaxPageHtmlMainDiv(RenderRequest, RenderResponse, BufferedWriter, String)} - * <li> - * {@link #getVaadinConfigurationMap(RenderRequest, RenderResponse, Application, String)} - * <li> - * {@link #writeAjaxPageHtmlVaadinScripts(RenderRequest, RenderResponse, BufferedWriter, Application, String)} - * </ul> - * - * @param request - * the portlet request. - * @param response - * the portlet response to write to. - * @param window - * @param application - * @throws IOException - * if the writing failed due to input/output error. - * @throws MalformedURLException - * if the application is denied access the persistent data store - * represented by the given URL. - * @throws PortletException - */ - protected void writeAjaxPage(RenderRequest request, - RenderResponse response, Window window, Application application) - throws IOException, MalformedURLException, PortletException { - - response.setContentType("text/html"); - final BufferedWriter page = new BufferedWriter(new OutputStreamWriter( - response.getPortletOutputStream(), "UTF-8")); - - // TODO Currently, we can only load widgetsets and themes from the - // portal - - String themeName = getThemeForWindow(request, window); - - writeAjaxPageHtmlVaadinScripts(request, response, page, application, - themeName); - - /*- Add classnames; - * .v-app - * .v-app-loading - * .v-app-<simpleName for app class> - * .v-theme-<themeName, remove non-alphanum> - */ - String appClass = "v-app-"; - try { - appClass += getApplicationClass().getSimpleName(); - } catch (ClassNotFoundException e) { - appClass += "unknown"; - logger.log(Level.SEVERE, "Could not find application class", e); - } - String themeClass = "v-theme-" - + themeName.replaceAll("[^a-zA-Z0-9]", ""); - - String classNames = "v-app " + themeClass + " " + appClass; - - String style = getApplicationProperty(PORTLET_PARAMETER_STYLE); - String divStyle = ""; - if (style != null) { - divStyle = "style=\"" + style + "\""; - } - - writeAjaxPageHtmlMainDiv(request, response, page, - getApplicationDomId(request), classNames, divStyle); - - page.close(); - } - - /** - * Creates and returns a unique ID for the DIV where the application is to - * be rendered. We need to generate a unique ID because some portals already - * create a DIV with the portlet's Window ID as the DOM ID. - * - * @param request - * PortletRequest - * @return the id to use in the DOM - */ - private String getApplicationDomId(PortletRequest request) { - return "v-" + request.getWindowID(); - } - - /** - * This method writes the scripts to load the widgetset and the themes as - * well as define Vaadin configuration parameters on the HTML fragment that - * starts the actual Vaadin application. - * - * @param request - * @param response - * @param writer - * @param application - * @param themeName - * @throws IOException - * @throws PortletException - */ - protected void writeAjaxPageHtmlVaadinScripts(RenderRequest request, - RenderResponse response, final BufferedWriter writer, - Application application, String themeName) throws IOException, - PortletException { - String themeURI = getThemeURI(themeName, request); - - // fixed base theme to use - all portal pages with Vaadin - // applications will load this exactly once - String portalTheme = getPortalProperty(PORTAL_PARAMETER_VAADIN_THEME, - request.getPortalContext()); - - writer.write("<script type=\"text/javascript\">\n"); - writer.write("if(!vaadin || !vaadin.vaadinConfigurations) {\n " - + "if(!vaadin) { var vaadin = {}} \n" - + "vaadin.vaadinConfigurations = {};\n" - + "if (!vaadin.themesLoaded) { vaadin.themesLoaded = {}; }\n"); - if (!isProductionMode()) { - writer.write("vaadin.debug = true;\n"); - } - - writeAjaxPageScriptWidgetset(request, response, writer); - - Map<String, String> config = getVaadinConfigurationMap(request, - response, application, themeURI); - writeAjaxPageScriptConfigurations(request, response, writer, config); - - writer.write("</script>\n"); - - writeAjaxPageHtmlTheme(request, writer, themeName, themeURI, - portalTheme); - - // TODO Warn if widgetset has not been loaded after 15 seconds - } - - /** - * Writes the script to load the widgetset on the HTML fragment created by - * the portlet. - * - * @param request - * @param response - * @param writer - * @throws IOException - */ - protected void writeAjaxPageScriptWidgetset(RenderRequest request, - RenderResponse response, final BufferedWriter writer) - throws IOException { - String requestWidgetset = getApplicationOrSystemProperty( - PARAMETER_WIDGETSET, null); - String sharedWidgetset = getPortalProperty( - PORTAL_PARAMETER_VAADIN_WIDGETSET, request.getPortalContext()); - - String widgetset; - if (requestWidgetset != null) { - widgetset = requestWidgetset; - } else if (sharedWidgetset != null) { - widgetset = sharedWidgetset; - } else { - widgetset = DEFAULT_WIDGETSET; - } - String widgetsetURL = getWidgetsetURL(widgetset, request); - writer.write("document.write('<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" " - + "style=\"position:absolute;width:0;height:0;border:0;overflow:" - + "hidden;opacity:0;top:-100px;left:-100px;\" src=\"javascript:false\"></iframe>');\n"); - writer.write("document.write(\"<script language='javascript' src='" - + widgetsetURL + "'><\\/script>\");\n}\n"); - } - - /** - * Returns the configuration parameters to pass to the client. - * - * To add configuration parameters for the client, override, call the super - * method and then modify the map. Overriding this method may also require - * client side changes in {@link ApplicationConnection} and - * {@link ApplicationConfiguration}. - * - * Note that this method must escape and quote the values when appropriate. - * - * The map returned is typically a {@link LinkedHashMap} to preserve - * insertion order, but it is not guaranteed to be one. - * - * @param request - * @param response - * @param application - * @param themeURI - * @return modifiable Map from parameter name to its full value - * @throws PortletException - */ - protected Map<String, String> getVaadinConfigurationMap( - RenderRequest request, RenderResponse response, - Application application, String themeURI) throws PortletException { - Map<String, String> config = new LinkedHashMap<String, String>(); - - /* - * We need this in order to get uploads to work. TODO this is not needed - * for uploads anymore, check if this is needed for some other things - */ - PortletURL appUri = response.createActionURL(); - config.put("appUri", "'" + appUri.toString() + "'"); - config.put("usePortletURLs", "true"); - ResourceURL uidlUrlBase = response.createResourceURL(); - uidlUrlBase.setResourceID("UIDL"); - config.put("portletUidlURLBase", "'" + uidlUrlBase.toString() + "'"); - config.put("pathInfo", "''"); - config.put("themeUri", "'" + themeURI + "'"); - - String versionInfo = "{vaadinVersion:\"" - + AbstractApplicationServlet.VERSION - + "\",applicationVersion:\"" + application.getVersion() + "\"}"; - config.put("versionInfo", versionInfo); - - // Get system messages - Application.SystemMessages systemMessages = null; - try { - systemMessages = getSystemMessages(); - } catch (SystemMessageException e) { - // failing to get the system messages is always a problem - throw new PortletException("Failed to obtain system messages!", e); - } - if (systemMessages != null) { - // Write the CommunicationError -message to client - String caption = systemMessages.getCommunicationErrorCaption(); - if (caption != null) { - caption = "\"" + caption + "\""; - } - String message = systemMessages.getCommunicationErrorMessage(); - if (message != null) { - message = "\"" + message + "\""; - } - String url = systemMessages.getCommunicationErrorURL(); - if (url != null) { - url = "\"" + url + "\""; - } - - config.put("\"comErrMsg\"", "{" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}"); - - // Write the AuthenticationError -message to client - caption = systemMessages.getAuthenticationErrorCaption(); - if (caption != null) { - caption = "\"" + caption + "\""; - } - message = systemMessages.getAuthenticationErrorMessage(); - if (message != null) { - message = "\"" + message + "\""; - } - url = systemMessages.getAuthenticationErrorURL(); - if (url != null) { - url = "\"" + url + "\""; - } - - config.put("\"authErrMsg\"", "{" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}"); - } - - return config; - } - - /** - * Constructs the Vaadin configuration section for - * {@link ApplicationConnection} and {@link ApplicationConfiguration}. - * - * Typically this method should not be overridden. Instead, modify - * {@link #getVaadinConfigurationMap(RenderRequest, RenderResponse, Application, String)} - * . - * - * @param request - * @param response - * @param writer - * @param config - * @throws IOException - * @throws PortletException - */ - protected void writeAjaxPageScriptConfigurations(RenderRequest request, - RenderResponse response, final BufferedWriter writer, - Map<String, String> config) throws IOException, PortletException { - - writer.write("vaadin.vaadinConfigurations[\"" - + getApplicationDomId(request) + "\"] = {"); - - Iterator<String> keyIt = config.keySet().iterator(); - while (keyIt.hasNext()) { - String key = keyIt.next(); - writer.write(key + ": " + config.get(key)); - if (keyIt.hasNext()) { - writer.write(", "); - } - } - - writer.write("};\n"); - } - - /** - * Writes the Vaadin theme loading section of the portlet HTML. Loads both - * the portal theme and the portlet theme in this order, skipping loading of - * themes that are already loaded (matched by name). - * - * @param request - * @param writer - * @param themeName - * @param themeURI - * @param portalTheme - * @throws IOException - */ - protected void writeAjaxPageHtmlTheme(RenderRequest request, - final BufferedWriter writer, String themeName, String themeURI, - String portalTheme) throws IOException { - writer.write("<script type=\"text/javascript\">\n"); - - if (portalTheme == null) { - portalTheme = DEFAULT_THEME_NAME; - } - - writer.write("if(!vaadin.themesLoaded['" + portalTheme + "']) {\n"); - writer.write("var defaultStylesheet = document.createElement('link');\n"); - writer.write("defaultStylesheet.setAttribute('rel', 'stylesheet');\n"); - writer.write("defaultStylesheet.setAttribute('type', 'text/css');\n"); - writer.write("defaultStylesheet.setAttribute('href', '" - + getThemeURI(portalTheme, request) + "/styles.css');\n"); - writer.write("document.getElementsByTagName('head')[0].appendChild(defaultStylesheet);\n"); - writer.write("vaadin.themesLoaded['" + portalTheme + "'] = true;\n}\n"); - - if (!portalTheme.equals(themeName)) { - writer.write("if(!vaadin.themesLoaded['" + themeName + "']) {\n"); - writer.write("var stylesheet = document.createElement('link');\n"); - writer.write("stylesheet.setAttribute('rel', 'stylesheet');\n"); - writer.write("stylesheet.setAttribute('type', 'text/css');\n"); - writer.write("stylesheet.setAttribute('href', '" + themeURI - + "/styles.css');\n"); - writer.write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n"); - writer.write("vaadin.themesLoaded['" + themeName - + "'] = true;\n}\n"); - } - - writer.write("</script>\n"); - } - - /** - * Method to write the div element into which that actual Vaadin application - * is rendered. - * <p> - * Override this method if you want to add some custom html around around - * the div element into which the actual Vaadin application will be - * rendered. - * - * @param request - * @param response - * @param writer - * @param id - * @param classNames - * @param divStyle - * @throws IOException - */ - protected void writeAjaxPageHtmlMainDiv(RenderRequest request, - RenderResponse response, final BufferedWriter writer, String id, - String classNames, String divStyle) throws IOException { - writer.write("<div id=\"" + id + "\" class=\"" + classNames + "\" " - + divStyle + ">"); - writer.write("<div class=\"v-app-loading\"></div>"); - writer.write("</div>\n"); - writer.write("<noscript>" + getNoScriptMessage() + "</noscript>"); - } - - /** - * Returns a message printed for browsers without scripting support or if - * browsers scripting support is disabled. - */ - protected String getNoScriptMessage() { - return "You have to enable javascript in your browser to use an application built with Vaadin."; - } - - /** - * Returns the theme for given request/window - * - * @param request - * @param window - * @return - */ - protected String getThemeForWindow(PortletRequest request, Window window) { - // Finds theme name - String themeName; - - // theme defined for the window? - themeName = window.getTheme(); - - if (themeName == null) { - // no, is the default theme defined by the portal? - themeName = getPortalProperty( - Constants.PORTAL_PARAMETER_VAADIN_THEME, - request.getPortalContext()); - } - - if (themeName == null) { - // no, using the default theme defined by Vaadin - themeName = DEFAULT_THEME_NAME; - } - - return themeName; - } - protected abstract Class<? extends Application> getApplicationClass() throws ClassNotFoundException; @@ -1315,6 +986,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet throws PortletException { try { final Application application = getApplicationClass().newInstance(); + application.setRootPreserved(true); return application; } catch (final IllegalAccessException e) { throw new PortletException("getNewApplication failed", e); @@ -1462,162 +1134,6 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } /** - * Returns a portal configuration property. - * - * Liferay is handled separately as - * {@link PortalContext#getProperty(String)} does not return portal - * properties from e.g. portal-ext.properties . - * - * @param name - * @param context - * @return - */ - protected static String getPortalProperty(String name, PortalContext context) { - boolean isLifeRay = context.getPortalInfo().toLowerCase() - .contains("liferay"); - - // TODO test on non-LifeRay platforms - - String value; - if (isLifeRay) { - value = getLifeRayPortalProperty(name); - } else { - value = context.getProperty(name); - } - - return value; - } - - private static String getLifeRayPortalProperty(String name) { - String value; - try { - value = PropsUtil.get(name); - } catch (Exception e) { - value = null; - } - return value; - } - - /** - * Try to get an HTTP header value from a request using portal specific - * APIs. - * - * @param name - * HTTP header name - * @return the value of the header (empty string if defined without a value, - * null if the parameter is not present or retrieving it failed) - */ - private static String getHTTPHeader(PortletRequest request, String name) { - String value = null; - String portalInfo = request.getPortalContext().getPortalInfo() - .toLowerCase(); - if (portalInfo.contains("liferay")) { - value = getLiferayHTTPHeader(request, name); - } else if (portalInfo.contains("gatein")) { - value = getGateInHTTPHeader(request, name); - } - return value; - } - - /** - * Try to get the value of a HTTP request parameter from a portlet request - * using portal specific APIs. It is not possible to get the HTTP request - * parameters using the official Portlet 2.0 API. - * - * @param name - * HTTP request parameter name - * @return the value of the parameter (empty string if parameter defined - * without a value, null if the parameter is not present or - * retrieving it failed) - */ - private static String getHTTPRequestParameter(PortletRequest request, - String name) { - String value = request.getParameter(name); - if (value == null) { - String portalInfo = request.getPortalContext().getPortalInfo() - .toLowerCase(); - if (portalInfo.contains("liferay")) { - value = getLiferayHTTPRequestParameter(request, name); - } else if (portalInfo.contains("gatein")) { - value = getGateInHTTPRequestParameter(request, name); - } - } - return value; - } - - private static String getGateInHTTPRequestParameter(PortletRequest request, - String name) { - String value = null; - try { - Method getRealReq = request.getClass().getMethod("getRealRequest"); - HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq - .invoke(request); - value = origRequest.getParameter(name); - } catch (Exception e) { - // do nothing - not on GateIn simple-portal - } - return value; - } - - private static String getLiferayHTTPRequestParameter( - PortletRequest request, String name) { - try { - // httpRequest = PortalUtil.getHttpServletRequest(request); - HttpServletRequest httpRequest = (HttpServletRequest) PortalClassInvoker - .invoke("com.liferay.portal.util.PortalUtil", - "getHttpServletRequest", request); - - // httpRequest = - // PortalUtil.getOriginalServletRequest(httpRequest); - httpRequest = (HttpServletRequest) PortalClassInvoker.invoke( - "com.liferay.portal.util.PortalUtil", - "getOriginalServletRequest", httpRequest); - if (httpRequest != null) { - return httpRequest.getParameter(name); - } - } catch (Exception e) { - // ignore and return null - unable to get the original request - } - return null; - } - - private static String getGateInHTTPHeader(PortletRequest request, - String name) { - String value = null; - try { - Method getRealReq = request.getClass().getMethod("getRealRequest"); - HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq - .invoke(request); - value = origRequest.getHeader(name); - } catch (Exception e) { - // do nothing - not on GateIn simple-portal - } - return value; - } - - private static String getLiferayHTTPHeader(PortletRequest request, - String name) { - try { - // httpRequest = PortalUtil.getHttpServletRequest(request); - HttpServletRequest httpRequest = (HttpServletRequest) PortalClassInvoker - .invoke("com.liferay.portal.util.PortalUtil", - "getHttpServletRequest", request); - - // httpRequest = - // PortalUtil.getOriginalServletRequest(httpRequest); - httpRequest = (HttpServletRequest) PortalClassInvoker.invoke( - "com.liferay.portal.util.PortalUtil", - "getOriginalServletRequest", httpRequest); - if (httpRequest != null) { - return httpRequest.getHeader(name); - } - } catch (Exception e) { - // ignore and return null - unable to get the original request - } - return null; - } - - /** * * Gets the application context for a PortletSession. If no context is * currently stored in a session a new context is created and stored in the diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 04ea423004..799271b979 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -15,15 +15,14 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.Collection; -import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; -import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -37,14 +36,16 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.vaadin.Application; +import com.vaadin.Application.ApplicationStartEvent; import com.vaadin.Application.SystemMessages; -import com.vaadin.terminal.DownloadStream; -import com.vaadin.terminal.ParameterHandler; +import com.vaadin.terminal.DeploymentConfiguration; import com.vaadin.terminal.Terminal; import com.vaadin.terminal.ThemeResource; -import com.vaadin.terminal.URIHandler; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.ui.Window; +import com.vaadin.terminal.gwt.server.AbstractCommunicationManager.Callback; +import com.vaadin.ui.Root; /** * Abstract implementation of the ApplicationServlet which handles all @@ -64,6 +65,25 @@ import com.vaadin.ui.Window; public abstract class AbstractApplicationServlet extends HttpServlet implements Constants { + private static class AbstractApplicationServletWrapper implements Callback { + + private final AbstractApplicationServlet servlet; + + public AbstractApplicationServletWrapper( + AbstractApplicationServlet servlet) { + this.servlet = servlet; + } + + public void criticalNotification(WrappedRequest request, + WrappedResponse response, String cap, String msg, + String details, String outOfSyncURL) throws IOException { + servlet.criticalNotification( + WrappedHttpServletRequest.cast(request), + ((WrappedHttpServletResponse) response), cap, msg, details, + outOfSyncURL); + } + } + // TODO Move some (all?) of the constants to a separate interface (shared // with portlet) @@ -115,67 +135,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } } - /** - * If the attribute is present in the request, a html fragment will be - * written instead of a whole page. - * - * It is set to "true" by the {@link ApplicationPortlet} (Portlet 1.0) and - * read by {@link AbstractApplicationServlet}. - */ - public static final String REQUEST_FRAGMENT = ApplicationServlet.class - .getName() + ".fragment"; - /** - * This request attribute forces widgetsets to be loaded from under the - * specified base path; e.g shared widgetset for all portlets in a portal. - * - * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on - * {@link Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH} and read by - * {@link AbstractApplicationServlet}. - */ - public static final String REQUEST_VAADIN_STATIC_FILE_PATH = ApplicationServlet.class - .getName() + ".widgetsetPath"; - /** - * This request attribute forces widgetset used; e.g for portlets that can - * not have different widgetsets. - * - * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on - * {@link ApplicationPortlet.PORTLET_PARAMETER_WIDGETSET} and read by - * {@link AbstractApplicationServlet}. - */ - public static final String REQUEST_WIDGETSET = ApplicationServlet.class - .getName() + ".widgetset"; - /** - * This request attribute indicates the shared widgetset (e.g. portal-wide - * default widgetset). - * - * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on - * {@link Constants.PORTAL_PARAMETER_VAADIN_WIDGETSET} and read by - * {@link AbstractApplicationServlet}. - */ - public static final String REQUEST_SHARED_WIDGETSET = ApplicationServlet.class - .getName() + ".sharedWidgetset"; - /** - * If set, do not load the default theme but assume that loading it is - * handled e.g. by ApplicationPortlet. - * - * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on - * {@link Constants.PORTAL_PARAMETER_VAADIN_THEME} and read by - * {@link AbstractApplicationServlet}. - */ - public static final String REQUEST_DEFAULT_THEME = ApplicationServlet.class - .getName() + ".defaultThemeUri"; - /** - * This request attribute is used to add styles to the main element. E.g - * "height:500px" generates a style="height:500px" to the main element, - * useful from some embedding situations (e.g portlet include.) - * - * It is typically set by the {@link ApplicationPortlet} (Portlet 1.0) based - * on {@link ApplicationPortlet.PORTLET_PARAMETER_STYLE} and read by - * {@link AbstractApplicationServlet}. - */ - public static final String REQUEST_APPSTYLE = ApplicationServlet.class - .getName() + ".style"; - private Properties applicationProperties; private boolean productionMode = false; @@ -183,6 +142,45 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements private final String resourcePath = null; private int resourceCacheTime = 3600; + + private DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration() { + public String getStaticFileLocation(WrappedRequest request) { + HttpServletRequest servletRequest = WrappedHttpServletRequest + .cast(request); + return AbstractApplicationServlet.this + .getStaticFilesLocation(servletRequest); + } + + public String getConfiguredWidgetset(WrappedRequest request) { + return getApplicationOrSystemProperty( + AbstractApplicationServlet.PARAMETER_WIDGETSET, + AbstractApplicationServlet.DEFAULT_WIDGETSET); + } + + public String getConfiguredTheme(WrappedRequest request) { + // Use the default + return AbstractApplicationServlet.getDefaultTheme(); + } + + public String getApplicationOrSystemProperty(String propertyName, + String defaultValue) { + return AbstractApplicationServlet.this + .getApplicationOrSystemProperty(propertyName, defaultValue); + } + + public boolean isStandalone(WrappedRequest request) { + return true; + } + + public ClassLoader getClassLoader() { + try { + return AbstractApplicationServlet.this.getClassLoader(); + } catch (ServletException e) { + throw new RuntimeException(e); + } + } + }; + static final String UPLOAD_URL_PREFIX = "APP/UPLOAD/"; /** @@ -201,17 +199,9 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements public void init(javax.servlet.ServletConfig servletConfig) throws javax.servlet.ServletException { super.init(servletConfig); - - // Stores the application parameters into Properties object applicationProperties = new Properties(); - for (final Enumeration<String> e = servletConfig - .getInitParameterNames(); e.hasMoreElements();) { - final String name = e.nextElement(); - applicationProperties.setProperty(name, - servletConfig.getInitParameter(name)); - } - // Overrides with server.xml parameters + // Read default parameters from server.xml final ServletContext context = servletConfig.getServletContext(); for (final Enumeration<String> e = context.getInitParameterNames(); e .hasMoreElements();) { @@ -219,6 +209,15 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements applicationProperties.setProperty(name, context.getInitParameter(name)); } + + // Override with application config from web.xml + for (final Enumeration<String> e = servletConfig + .getInitParameterNames(); e.hasMoreElements();) { + final String name = e.nextElement(); + applicationProperties.setProperty(name, + servletConfig.getInitParameter(name)); + } + checkProductionMode(); checkCrossSiteProtection(); checkResourceCacheTime(); @@ -251,14 +250,9 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements private void checkProductionMode() { // Check if the application is in production mode. - // We are in production mode if Debug=false or productionMode=true - if (getApplicationOrSystemProperty(SERVLET_PARAMETER_DEBUG, "true") - .equals("false")) { - // "Debug=true" is the old way and should no longer be used - productionMode = true; - } else if (getApplicationOrSystemProperty( - SERVLET_PARAMETER_PRODUCTION_MODE, "false").equals("true")) { - // "productionMode=true" is the real way to do it + // We are in production mode if productionMode=true + if (getApplicationOrSystemProperty(SERVLET_PARAMETER_PRODUCTION_MODE, + "false").equals("true")) { productionMode = true; } @@ -341,7 +335,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * the Default to be used. * @return String value or default if not found */ - private String getApplicationOrSystemProperty(String parameterName, + String getApplicationOrSystemProperty(String parameterName, String defaultValue) { String val = null; @@ -397,14 +391,20 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * @throws IOException * if the request for the TRACE cannot be handled. */ - @SuppressWarnings("unchecked") @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - RequestTimer.RequestWrapper wrappedRequest = new RequestTimer.RequestWrapper( - request); - RequestTimer requestTimer = RequestTimer.get(wrappedRequest); - requestTimer.start(wrappedRequest); + service(createWrappedRequest(request), createWrappedResponse(response)); + } + + private void service(WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws ServletException, + IOException { + AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( + this); + + RequestTimer requestTimer = RequestTimer.get(request); + requestTimer.start(request); RequestType requestType = getRequestType(request); if (!ensureCookiesEnabled(requestType, request, response)) { @@ -449,6 +449,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (application == null) { return; } + Application.setCurrentApplication(application); /* * Get or create a WebApplicationContext and an ApplicationManager @@ -460,7 +461,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements .getApplicationManager(application, this); /* Update browser information from the request */ - updateBrowserProperties(webApplicationContext.getBrowser(), request); + webApplicationContext.getBrowser().updateRequestDetails(request); /* * Call application requestStart before Application.init() is called @@ -472,7 +473,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements requestStarted = true; } - // Start the newly created application + // Start the application if it's newly created startApplication(request, application, webApplicationContext); /* @@ -484,14 +485,21 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(request, response); + applicationManager.handleFileUpload(application, request, + response); return; } else if (requestType == RequestType.UIDL) { // Handles AJAX UIDL requests - Window window = applicationManager.getApplicationWindow( - request, this, application, null); - applicationManager.handleUidlRequest(request, response, this, - window); + Root root = application.getRootForRequest(request); + if (root == null) { + throw new ServletException(ERROR_NO_WINDOW_FOUND); + } + applicationManager.handleUidlRequest(request, response, + servletWrapper, root); + return; + } else if (requestType == RequestType.BROWSER_DETAILS) { + applicationManager.handleBrowserDetailsRequest(request, + response, application); return; } @@ -502,34 +510,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements return; } - // Finds the window within the application - Window window = getApplicationWindow(request, applicationManager, - application); - if (window == null) { - throw new ServletException(ERROR_NO_WINDOW_FOUND); - } - - // Sets terminal type for the window, if not already set - if (window.getTerminal() == null) { - window.setTerminal(webApplicationContext.getBrowser()); - } - - // Handle parameters - final Map<String, String[]> parameters = request.getParameterMap(); - if (window != null && parameters != null) { - window.handleParameters(parameters); - } - - /* - * Call the URI handlers and if this turns out to be a download - * request, send the file to the client - */ - if (handleURI(applicationManager, window, request, response)) { + if (applicationManager.handleApplicationRequest(request, response)) { return; } - - // Send initial AJAX page that kickstarts a Vaadin application - writeAjaxPage(request, response, window, application); + // TODO Should return 404 error here and not do anything more } catch (final SessionExpiredException e) { // Session has expired, notify user @@ -548,18 +532,53 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } } finally { - if (requestStarted) { - ((HttpServletRequestListener) application).onRequestEnd( - request, response); + try { + if (requestStarted) { + ((HttpServletRequestListener) application) + .onRequestEnd(request, response); + } + } finally { + Root.setCurrentRoot(null); + Application.setCurrentApplication(null); } requestTimer.stop(); - RequestTimer.set(wrappedRequest, requestTimer); + RequestTimer.set(request, requestTimer); } } } + private WrappedHttpServletResponse createWrappedResponse( + HttpServletResponse response) { + WrappedHttpServletResponse wrappedResponse = new WrappedHttpServletResponse( + response, getDeploymentConfiguration()); + return wrappedResponse; + } + + /** + * Create a wrapped request for a http servlet request. This method can be + * overridden if the wrapped request should have special properties. + * + * @param request + * the original http servlet request + * @return a wrapped request for the original request + */ + protected WrappedHttpServletRequest createWrappedRequest( + HttpServletRequest request) { + return new WrappedHttpServletRequest(request, + getDeploymentConfiguration()); + } + + /** + * Gets a the deployment configuration for this servlet. + * + * @return the deployment configuration + */ + protected DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + /** * Check that cookie support is enabled in the browser. Only checks UIDL * requests. @@ -593,23 +612,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements return true; } - private void updateBrowserProperties(WebBrowser browser, - HttpServletRequest request) { - // request based details updated always - browser.updateRequestDetails(request.getLocale(), - request.getRemoteAddr(), request.isSecure(), - request.getHeader("user-agent")); - if (request.getParameter("repaintAll") != null) { - browser.updateClientSideDetails(request.getParameter("sw"), - request.getParameter("sh"), request.getParameter("cw"), - request.getParameter("ch"), request.getParameter("tzo"), - request.getParameter("rtzo"), request.getParameter("dstd"), - request.getParameter("dston"), - request.getParameter("curdate"), - request.getParameter("td") != null); - } - } - protected ClassLoader getClassLoader() throws ServletException { // Gets custom class loader final String classLoaderName = getApplicationOrSystemProperty( @@ -818,7 +820,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements /* * UIDL request contains valid repaintAll=1 event, the user probably * wants to initiate a new application through a custom index.html - * without using writeAjaxPage. + * without using the bootstrap page. */ return true; @@ -864,101 +866,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } /** - * Handles the requested URI. An application can add handlers to do special - * processing, when a certain URI is requested. The handlers are invoked - * before any windows URIs are processed and if a DownloadStream is returned - * it is sent to the client. - * - * @param stream - * the download stream. - * - * @param request - * the HTTP request instance. - * @param response - * the HTTP response to write to. - * @throws IOException - * - * @see com.vaadin.terminal.URIHandler - */ - private void handleDownload(DownloadStream stream, - HttpServletRequest request, HttpServletResponse response) - throws IOException { - - if (stream.getParameter("Location") != null) { - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.addHeader("Location", stream.getParameter("Location")); - return; - } - - // Download from given stream - final InputStream data = stream.getStream(); - if (data != null) { - - OutputStream out = null; - try { - // Sets content type - response.setContentType(stream.getContentType()); - - // Sets cache headers - final long cacheTime = stream.getCacheTime(); - if (cacheTime <= 0) { - response.setHeader("Cache-Control", "no-cache"); - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - } else { - response.setHeader("Cache-Control", "max-age=" + cacheTime - / 1000); - response.setDateHeader("Expires", - System.currentTimeMillis() + cacheTime); - response.setHeader("Pragma", "cache"); // Required to apply - // caching in some - // Tomcats - } - - // Copy download stream parameters directly - // to HTTP headers. - final Iterator<String> i = stream.getParameterNames(); - if (i != null) { - while (i.hasNext()) { - final String param = i.next(); - response.setHeader(param, stream.getParameter(param)); - } - } - - // suggest local filename from DownloadStream if - // Content-Disposition - // not explicitly set - String contentDispositionValue = stream - .getParameter("Content-Disposition"); - if (contentDispositionValue == null) { - contentDispositionValue = "filename=\"" - + stream.getFileName() + "\""; - response.setHeader("Content-Disposition", - contentDispositionValue); - } - - int bufferSize = stream.getBufferSize(); - if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) { - bufferSize = DEFAULT_BUFFER_SIZE; - } - final byte[] buffer = new byte[bufferSize]; - int bytesRead = 0; - - out = response.getOutputStream(); - - while ((bytesRead = data.read(buffer)) > 0) { - out.write(buffer, 0, bytesRead); - out.flush(); - } - } finally { - AbstractCommunicationManager.tryToCloseStream(out); - AbstractCommunicationManager.tryToCloseStream(data); - } - } - - } - - /** * Creates a new application and registers it into WebApplicationContext * (aka session). This is not meant to be overridden. Override * getNewApplication to create the application instance in a custom way. @@ -1002,42 +909,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } /** - * Returns the theme for given request/window - * - * @param request - * @param window - * @return - */ - private String getThemeForWindow(HttpServletRequest request, Window window) { - // Finds theme name - String themeName; - - if (request.getParameter(URL_PARAMETER_THEME) != null) { - themeName = request.getParameter(URL_PARAMETER_THEME); - } else { - themeName = window.getTheme(); - } - - if (themeName == null) { - // no explicit theme for window defined - if (request.getAttribute(REQUEST_DEFAULT_THEME) != null) { - // the default theme is defined in request (by portal) - themeName = (String) request - .getAttribute(REQUEST_DEFAULT_THEME); - } else { - // using the default theme defined by Vaadin - themeName = getDefaultTheme(); - } - } - - // XSS preventation, theme names shouldn't contain special chars anyway. - // The servlet denies them via url parameter. - themeName = stripSpecialChars(themeName); - - return themeName; - } - - /** * A helper method to strip away characters that might somehow be used for * XSS attacs. Leaves at least alphanumeric characters intact. Also removes * eg. ( and ), so values should be safe in javascript too. @@ -1070,34 +941,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements return DEFAULT_THEME_NAME; } - /** - * Calls URI handlers for the request. If an URI handler returns a - * DownloadStream the stream is passed to the client for downloading. - * - * @param applicationManager - * @param window - * @param request - * @param response - * @return true if an DownloadStream was sent to the client, false otherwise - * @throws IOException - */ - protected boolean handleURI(CommunicationManager applicationManager, - Window window, HttpServletRequest request, - HttpServletResponse response) throws IOException { - // Handles the URI - DownloadStream download = applicationManager.handleURI(window, request, - response, this); - - // A download request - if (download != null) { - // Client downloads an resource - handleDownload(download, request, response); - return true; - } - - return false; - } - void handleServiceSessionExpired(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { @@ -1205,8 +1048,9 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // Initial locale comes from the request Locale locale = request.getLocale(); application.setLocale(locale); - application.start(applicationUrl, applicationProperties, - webApplicationContext); + application.start(new ApplicationStartEvent(applicationUrl, + applicationProperties, webApplicationContext, + isProductionMode())); } } @@ -1293,8 +1137,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // Find the modification timestamp long lastModifiedTime = 0; + URLConnection connection = null; try { - lastModifiedTime = resourceUrl.openConnection().getLastModified(); + connection = resourceUrl.openConnection(); + lastModifiedTime = connection.getLastModified(); // Remove milliseconds to avoid comparison problems (milliseconds // are not returned by the browser in the "If-Modified-Since" // header). @@ -1310,6 +1156,21 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements Level.FINEST, "Failed to find out last modified timestamp. Continuing without it.", e); + } finally { + if (connection instanceof URLConnection) { + try { + // Explicitly close the input stream to prevent it + // from remaining hanging + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700 + InputStream is = connection.getInputStream(); + if (is != null) { + is.close(); + } + } catch (IOException e) { + logger.log(Level.INFO, + "Error closing URLConnection input stream", e); + } + } } // Set type mime type if we can determine it based on the filename @@ -1441,12 +1302,14 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } protected enum RequestType { - FILE_UPLOAD, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE; + FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE; } protected RequestType getRequestType(HttpServletRequest request) { if (isFileUploadRequest(request)) { return RequestType.FILE_UPLOAD; + } else if (isBrowserDetailsRequest(request)) { + return RequestType.BROWSER_DETAILS; } else if (isUIDLRequest(request)) { return RequestType.UIDL; } else if (isStaticResourceRequest(request)) { @@ -1460,6 +1323,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } + private static boolean isBrowserDetailsRequest(HttpServletRequest request) { + return "POST".equals(request.getMethod()) + && request.getParameter("browserDetails") != null; + } + private boolean isApplicationRequest(HttpServletRequest request) { String path = getRequestPathInfo(request); if (path != null && path.startsWith("/APP/")) { @@ -1526,13 +1394,25 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * @return */ protected SystemMessages getSystemMessages() { + Class<? extends Application> appCls = null; try { - Class<? extends Application> appCls = getApplicationClass(); - Method m = appCls.getMethod("getSystemMessages", (Class[]) null); - return (Application.SystemMessages) m.invoke(null, (Object[]) null); + appCls = getApplicationClass(); } catch (ClassNotFoundException e) { - // This should never happen + // Previous comment claimed that this should never happen throw new SystemMessageException(e); + } + return getSystemMessages(appCls); + } + + public static SystemMessages getSystemMessages( + Class<? extends Application> appCls) { + try { + if (appCls != null) { + Method m = appCls + .getMethod("getSystemMessages", (Class[]) null); + return (Application.SystemMessages) m.invoke(null, + (Object[]) null); + } } catch (SecurityException e) { throw new SystemMessageException( "Application.getSystemMessage() should be static public", e); @@ -1568,15 +1448,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements */ protected String getStaticFilesLocation(HttpServletRequest request) { - // request may have an attribute explicitly telling location (portal - // case) - String staticFileLocation = (String) request - .getAttribute(REQUEST_VAADIN_STATIC_FILE_PATH); - if (staticFileLocation != null) { - // TODO remove trailing slash if any? - return staticFileLocation; - } - return getWebApplicationsStaticFileLocation(request); } @@ -1659,484 +1530,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } /** - * This method writes the html host page (aka kickstart page) that starts - * the actual Vaadin application. - * <p> - * If one needs to override parts of the host page, it is suggested that one - * overrides on of several submethods which are called by this method: - * <ul> - * <li> {@link #setAjaxPageHeaders(HttpServletResponse)} - * <li> - * {@link #writeAjaxPageHtmlHeadStart(BufferedWriter, HttpServletRequest)} - * <li> - * {@link #writeAjaxPageHtmlHeader(BufferedWriter, String, String, HttpServletRequest)} - * <li> - * {@link #writeAjaxPageHtmlBodyStart(BufferedWriter, HttpServletRequest)} - * <li> - * {@link #writeAjaxPageHtmlVaadinScripts(Window, String, Application, BufferedWriter, String, String, String, HttpServletRequest)} - * <li> - * {@link #writeAjaxPageHtmlMainDiv(BufferedWriter, String, String, String, HttpServletRequest)} - * <li> {@link #writeAjaxPageHtmlBodyEnd(BufferedWriter)} - * </ul> - * - * @param request - * the HTTP request. - * @param response - * the HTTP response to write to. - * @param out - * @param unhandledParameters - * @param window - * @param terminalType - * @param theme - * @throws IOException - * if the writing failed due to input/output error. - * @throws MalformedURLException - * if the application is denied access the persistent data store - * represented by the given URL. - */ - protected void writeAjaxPage(HttpServletRequest request, - HttpServletResponse response, Window window, Application application) - throws IOException, MalformedURLException, ServletException { - - // e.g portlets only want a html fragment - boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null); - if (fragment) { - // if this is a fragment request, the actual application is put to - // request so ApplicationPortlet can save it for a later use - request.setAttribute(Application.class.getName(), application); - } - - final BufferedWriter page = new BufferedWriter(new OutputStreamWriter( - response.getOutputStream(), "UTF-8")); - - String title = ((window.getCaption() == null) ? "Vaadin 6" : window - .getCaption()); - - /* Fetch relative url to application */ - // don't use server and port in uri. It may cause problems with some - // virtual server configurations which lose the server name - String appUrl = getApplicationUrl(request).getPath(); - if (appUrl.endsWith("/")) { - appUrl = appUrl.substring(0, appUrl.length() - 1); - } - - String themeName = getThemeForWindow(request, window); - - String themeUri = getThemeUri(themeName, request); - - if (!fragment) { - setAjaxPageHeaders(response); - writeAjaxPageHtmlHeadStart(page, request); - writeAjaxPageHtmlHeader(page, title, themeUri, request); - writeAjaxPageHtmlBodyStart(page, request); - } - - String appId = appUrl; - if ("".equals(appUrl)) { - appId = "ROOT"; - } - appId = appId.replaceAll("[^a-zA-Z0-9]", ""); - // Add hashCode to the end, so that it is still (sort of) predictable, - // but indicates that it should not be used in CSS and such: - int hashCode = appId.hashCode(); - if (hashCode < 0) { - hashCode = -hashCode; - } - appId = appId + "-" + hashCode; - - writeAjaxPageHtmlVaadinScripts(window, themeName, application, page, - appUrl, themeUri, appId, request); - - /*- Add classnames; - * .v-app - * .v-app-loading - * .v-app-<simpleName for app class> - * .v-theme-<themeName, remove non-alphanum> - */ - - String appClass = "v-app-" + getApplicationCSSClassName(); - - String themeClass = ""; - if (themeName != null) { - themeClass = "v-theme-" + themeName.replaceAll("[^a-zA-Z0-9]", ""); - } else { - themeClass = "v-theme-" - + getDefaultTheme().replaceAll("[^a-zA-Z0-9]", ""); - } - - String classNames = "v-app " + themeClass + " " + appClass; - - String divStyle = null; - if (request.getAttribute(REQUEST_APPSTYLE) != null) { - divStyle = "style=\"" + request.getAttribute(REQUEST_APPSTYLE) - + "\""; - } - - writeAjaxPageHtmlMainDiv(page, appId, classNames, divStyle, request); - - if (!fragment) { - page.write("</body>\n</html>\n"); - } - - page.close(); - - } - - /** - * Returns the application class identifier for use in the application CSS - * class name in the root DIV. The application CSS class name is of form - * "v-app-"+getApplicationCSSClassName(). - * - * This method should normally not be overridden. - * - * @return The CSS class name to use in combination with "v-app-". - */ - protected String getApplicationCSSClassName() { - try { - return getApplicationClass().getSimpleName(); - } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, "getApplicationCSSClassName failed", e); - return "unknown"; - } - } - - /** - * Get the URI for the application theme. - * - * A portal-wide default theme is fetched from the portal shared resource - * directory (if any), other themes from the portlet. - * - * @param themeName - * @param request - * @return - */ - private String getThemeUri(String themeName, HttpServletRequest request) { - final String staticFilePath; - if (themeName.equals(request.getAttribute(REQUEST_DEFAULT_THEME))) { - // our window theme is the portal wide default theme, make it load - // from portals directory is defined - staticFilePath = getStaticFilesLocation(request); - } else { - /* - * theme is a custom theme, which is not necessarily located in - * portals VAADIN directory. Let the default servlet conf decide - * (omitting request parameter) the location. Note that theme can - * still be placed to portal directory with servlet parameter. - */ - staticFilePath = getWebApplicationsStaticFileLocation(request); - } - return staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName; - } - - /** - * Method to write the div element into which that actual Vaadin application - * is rendered. - * <p> - * Override this method if you want to add some custom html around around - * the div element into which the actual Vaadin application will be - * rendered. - * - * @param page - * @param appId - * @param classNames - * @param divStyle - * @param request - * @throws IOException - */ - protected void writeAjaxPageHtmlMainDiv(final BufferedWriter page, - String appId, String classNames, String divStyle, - HttpServletRequest request) throws IOException { - page.write("<div id=\"" + appId + "\" class=\"" + classNames + "\" " - + (divStyle != null ? divStyle : "") + ">"); - page.write("<div class=\"v-app-loading\"></div>"); - page.write("</div>\n"); - page.write("<noscript>" + getNoScriptMessage() + "</noscript>"); - } - - /** - * Method to write the script part of the page which loads needed Vaadin - * scripts and themes. - * <p> - * Override this method if you want to add some custom html around scripts. - * - * @param window - * @param themeName - * @param application - * @param page - * @param appUrl - * @param themeUri - * @param appId - * @param request - * @throws ServletException - * @throws IOException - */ - protected void writeAjaxPageHtmlVaadinScripts(Window window, - String themeName, Application application, - final BufferedWriter page, String appUrl, String themeUri, - String appId, HttpServletRequest request) throws ServletException, - IOException { - - // request widgetset takes precedence (e.g portlet include) - String requestWidgetset = (String) request - .getAttribute(REQUEST_WIDGETSET); - String sharedWidgetset = (String) request - .getAttribute(REQUEST_SHARED_WIDGETSET); - if (requestWidgetset == null && sharedWidgetset == null) { - // Use the value from configuration or DEFAULT_WIDGETSET. - // If no shared widgetset is specified, the default widgetset is - // assumed to be in the servlet/portlet itself. - requestWidgetset = getApplicationOrSystemProperty( - PARAMETER_WIDGETSET, DEFAULT_WIDGETSET); - } - - String widgetset; - String widgetsetBasePath; - if (requestWidgetset != null) { - widgetset = requestWidgetset; - widgetsetBasePath = getWebApplicationsStaticFileLocation(request); - } else { - widgetset = sharedWidgetset; - widgetsetBasePath = getStaticFilesLocation(request); - } - - widgetset = stripSpecialChars(widgetset); - - final String widgetsetFilePath = widgetsetBasePath + "/" - + WIDGETSET_DIRECTORY_PATH + widgetset + "/" + widgetset - + ".nocache.js" + createPreventCachingQueryString(); - - // Get system messages - Application.SystemMessages systemMessages = null; - try { - systemMessages = getSystemMessages(); - } catch (SystemMessageException e) { - // failing to get the system messages is always a problem - throw new ServletException("CommunicationError!", e); - } - - page.write("<script type=\"text/javascript\">\n"); - page.write("//<![CDATA[\n"); - page.write("if(!vaadin || !vaadin.vaadinConfigurations) {\n " - + "if(!vaadin) { var vaadin = {}} \n" - + "vaadin.vaadinConfigurations = {};\n" - + "if (!vaadin.themesLoaded) { vaadin.themesLoaded = {}; }\n"); - if (!isProductionMode()) { - page.write("vaadin.debug = true;\n"); - } - page.write("document.write('<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" " - + "style=\"position:absolute;width:0;height:0;border:0;overflow:" - + "hidden;\" src=\"javascript:false\"></iframe>');\n"); - page.write("document.write(\"<script language='javascript' src='" - + widgetsetFilePath + "'><\\/script>\");\n}\n"); - - page.write("vaadin.vaadinConfigurations[\"" + appId + "\"] = {"); - page.write("appUri:'" + appUrl + "', "); - - if (window != application.getMainWindow()) { - page.write("windowName: \"" - + JsonPaintTarget.escapeJSON(window.getName()) + "\", "); - } - if (isStandalone()) { - page.write("standalone: true, "); - } - page.write("themeUri:"); - page.write(themeUri != null ? "\"" + themeUri + "\"" : "null"); - page.write(", versionInfo : {vaadinVersion:\""); - page.write(VERSION); - page.write("\",applicationVersion:\""); - page.write(JsonPaintTarget.escapeJSON(application.getVersion())); - page.write("\"}"); - if (systemMessages != null) { - // Write the CommunicationError -message to client - String caption = systemMessages.getCommunicationErrorCaption(); - if (caption != null) { - caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\""; - } - String message = systemMessages.getCommunicationErrorMessage(); - if (message != null) { - message = "\"" + JsonPaintTarget.escapeJSON(message) + "\""; - } - String url = systemMessages.getCommunicationErrorURL(); - if (url != null) { - url = "\"" + JsonPaintTarget.escapeJSON(url) + "\""; - } - - page.write(",\"comErrMsg\": {" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}"); - - // Write the AuthenticationError -message to client - caption = systemMessages.getAuthenticationErrorCaption(); - if (caption != null) { - caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\""; - } - message = systemMessages.getAuthenticationErrorMessage(); - if (message != null) { - message = "\"" + JsonPaintTarget.escapeJSON(message) + "\""; - } - url = systemMessages.getAuthenticationErrorURL(); - if (url != null) { - url = "\"" + JsonPaintTarget.escapeJSON(url) + "\""; - } - - page.write(",\"authErrMsg\": {" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}"); - } - page.write("};\n//]]>\n</script>\n"); - - if (themeName != null) { - // Custom theme's stylesheet, load only once, in different - // script - // tag to be dominate styles injected by widget - // set - page.write("<script type=\"text/javascript\">\n"); - page.write("//<![CDATA[\n"); - page.write("if(!vaadin.themesLoaded['" + themeName + "']) {\n"); - page.write("var stylesheet = document.createElement('link');\n"); - page.write("stylesheet.setAttribute('rel', 'stylesheet');\n"); - page.write("stylesheet.setAttribute('type', 'text/css');\n"); - page.write("stylesheet.setAttribute('href', '" + themeUri - + "/styles.css');\n"); - page.write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n"); - page.write("vaadin.themesLoaded['" + themeName + "'] = true;\n}\n"); - page.write("//]]>\n</script>\n"); - } - - // Warn if the widgetset has not been loaded after 15 seconds on - // inactivity - page.write("<script type=\"text/javascript\">\n"); - page.write("//<![CDATA[\n"); - page.write("setTimeout('if (typeof " + widgetset.replace('.', '_') - + " == \"undefined\") {alert(\"Failed to load the widgetset: " - + widgetsetFilePath + "\")};',15000);\n" + "//]]>\n</script>\n"); - } - - /** - * To ensure the GWT kickstart scritp is downloaded each time (even if - * server caching is not set up right), we add a unique query parameter to - * the end of the script file. - * - * @return - */ - protected String createPreventCachingQueryString() { - return "?" + new Date().getTime(); - } - - /** - * @return true if the served application is considered to be the only or - * main content of the host page. E.g. various embedding solutions - * should override this to false. - */ - protected boolean isStandalone() { - return true; - } - - /** - * - * Method to open the body tag of the html kickstart page. - * <p> - * This method is responsible for closing the head tag and opening the body - * tag. - * <p> - * Override this method if you want to add some custom html to the page. - * - * @param page - * @param request - * @throws IOException - */ - protected void writeAjaxPageHtmlBodyStart(final BufferedWriter page, - final HttpServletRequest request) throws IOException { - page.write("\n</head>\n<body scroll=\"auto\" class=\"" - + ApplicationConnection.GENERATED_BODY_CLASSNAME + "\">\n"); - } - - /** - * Method to write the contents of head element in html kickstart page. - * <p> - * Override this method if you want to add some custom html to the header of - * the page. - * - * @param page - * @param title - * @param themeUri - * @param request - * @throws IOException - */ - protected void writeAjaxPageHtmlHeader(final BufferedWriter page, - String title, String themeUri, final HttpServletRequest request) - throws IOException { - page.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n"); - - WebBrowser browser = getApplicationContext(request.getSession()) - .getBrowser(); - if (browser.isIE()) { - // Chrome frame in all versions of IE (only if Chrome frame is - // installed) - page.write("<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\"/>\n"); - } - - page.write("<style type=\"text/css\">" - + "html, body {height:100%;margin:0;}</style>"); - - // Add favicon links - page.write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\"" - + themeUri + "/favicon.ico\" />"); - page.write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\"" - + themeUri + "/favicon.ico\" />"); - - page.write("<title>" + safeEscapeForHtml(title) + "</title>"); - } - - /** - * Method to write the beginning of the html page. - * <p> - * This method is responsible for writing appropriate doc type declarations - * and to open html and head tags. - * <p> - * Override this method if you want to add some custom html to the very - * beginning of the page. - * - * @param page - * @param request - * @throws IOException - */ - protected void writeAjaxPageHtmlHeadStart(final BufferedWriter page, - final HttpServletRequest request) throws IOException { - // write html header - page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD " - + "XHTML 1.0 Transitional//EN\" " - + "\"http://www.w3.org/TR/xhtml1/" - + "DTD/xhtml1-transitional.dtd\">\n"); - - page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\"" - + ">\n<head>\n"); - } - - /** - * Method to set http request headers for the Vaadin kickstart page. - * <p> - * Override this method if you need to customize http headers of the page. - * - * @param response - */ - protected void setAjaxPageHeaders(HttpServletResponse response) { - // Window renders are not cacheable - response.setHeader("Cache-Control", "no-cache"); - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - response.setContentType("text/html; charset=UTF-8"); - } - - /** - * Returns a message printed for browsers without scripting support or if - * browsers scripting support is disabled. - */ - protected String getNoScriptMessage() { - return "You have to enable javascript in your browser to use an application built with Vaadin."; - } - - /** * Gets the current application URL from request. * * @param request @@ -2264,52 +1657,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } /** - * Gets the existing application or create a new one. Get a window within an - * application based on the requested URI. - * - * @param request - * the HTTP Request. - * @param application - * the Application to query for window. - * @return Window matching the given URI or null if not found. - * @throws ServletException - * if an exception has occurred that interferes with the - * servlet's normal operation. - */ - protected Window getApplicationWindow(HttpServletRequest request, - CommunicationManager applicationManager, Application application) - throws ServletException { - - // Finds the window where the request is handled - Window assumedWindow = null; - String path = getRequestPathInfo(request); - - // Main window as the URI is empty - if (!(path == null || path.length() == 0 || path.equals("/"))) { - if (path.startsWith("/APP/")) { - // Use main window for application resources - return application.getMainWindow(); - } - String windowName = null; - if (path.charAt(0) == '/') { - path = path.substring(1); - } - final int index = path.indexOf('/'); - if (index < 0) { - windowName = path; - path = ""; - } else { - windowName = path.substring(0, index); - } - assumedWindow = application.getWindow(windowName); - - } - - return applicationManager.getApplicationWindow(request, this, - application, assumedWindow); - } - - /** * Returns the path info; note that this _can_ be different than * request.getPathInfo(). Examples where this might be useful: * <ul> @@ -2379,75 +1726,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements return WebApplicationContext.getApplicationContext(session); } - /** - * Implementation of ParameterHandler.ErrorEvent interface. - */ - public class ParameterHandlerErrorImpl implements - ParameterHandler.ErrorEvent, Serializable { - - private ParameterHandler owner; - - private Throwable throwable; - - /** - * Gets the contained throwable. - * - * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable() - */ - public Throwable getThrowable() { - return throwable; - } - - /** - * Gets the source ParameterHandler. - * - * @see com.vaadin.terminal.ParameterHandler.ErrorEvent#getParameterHandler() - */ - public ParameterHandler getParameterHandler() { - return owner; - } - - } - - /** - * Implementation of URIHandler.ErrorEvent interface. - */ - public class URIHandlerErrorImpl implements URIHandler.ErrorEvent, - Serializable { - - private final URIHandler owner; - - private final Throwable throwable; - - /** - * - * @param owner - * @param throwable - */ - private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) { - this.owner = owner; - this.throwable = throwable; - } - - /** - * Gets the contained throwable. - * - * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable() - */ - public Throwable getThrowable() { - return throwable; - } - - /** - * Gets the source URIHandler. - * - * @see com.vaadin.terminal.URIHandler.ErrorEvent#getURIHandler() - */ - public URIHandler getURIHandler() { - return owner; - } - } - public class RequestError implements Terminal.ErrorEvent, Serializable { private final Throwable throwable; diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 9a6bccebb8..b780f66a23 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -14,9 +14,10 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; +import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.net.URL; +import java.lang.reflect.Type; import java.security.GeneralSecurityException; import java.text.CharacterIterator; import java.text.DateFormat; @@ -37,36 +38,43 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.StringTokenizer; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import javax.portlet.PortletRequest; -import javax.portlet.PortletResponse; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - import com.vaadin.Application; import com.vaadin.Application.SystemMessages; -import com.vaadin.terminal.ApplicationResource; -import com.vaadin.terminal.DownloadStream; +import com.vaadin.RootRequiresMoreInformationException; +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.CombinedRequest; +import com.vaadin.terminal.LegacyPaint; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.Paintable; -import com.vaadin.terminal.Paintable.RepaintRequestEvent; +import com.vaadin.terminal.RequestHandler; import com.vaadin.terminal.StreamVariable; import com.vaadin.terminal.StreamVariable.StreamingEndEvent; import com.vaadin.terminal.StreamVariable.StreamingErrorEvent; import com.vaadin.terminal.Terminal.ErrorEvent; import com.vaadin.terminal.Terminal.ErrorListener; -import com.vaadin.terminal.URIHandler; +import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.server.BootstrapHandler.BootstrapContext; import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.Component; +import com.vaadin.ui.DirtyConnectorTracker; +import com.vaadin.ui.HasComponents; +import com.vaadin.ui.Panel; +import com.vaadin.ui.Root; import com.vaadin.ui.Window; /** @@ -75,196 +83,30 @@ import com.vaadin.ui.Window; * JavaScript) and the server side components. Its client side counterpart is * {@link ApplicationConnection}. * - * A server side component sends its state to the client in a paint request (see - * {@link Paintable} and {@link PaintTarget} on the server side). The client - * widget receives these paint requests as calls to - * {@link com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL()}. The client - * component communicates back to the server by sending a list of variable - * changes (see {@link ApplicationConnection#updateVariable()} and - * {@link VariableOwner#changeVariables(Object, Map)}). - * * TODO Document better! */ @SuppressWarnings("serial") -public abstract class AbstractCommunicationManager implements - Paintable.RepaintRequestListener, Serializable { +public abstract class AbstractCommunicationManager implements Serializable { private static final String DASHDASH = "--"; private static final Logger logger = Logger .getLogger(AbstractCommunicationManager.class.getName()); - /** - * Generic interface of a (HTTP or Portlet) request to the application. - * - * This is a wrapper interface that allows - * {@link AbstractCommunicationManager} to use a unified API. - * - * @see javax.servlet.ServletRequest - * @see javax.portlet.PortletRequest - * - * @author peholmst - */ - public interface Request { - - /** - * Gets a {@link Session} wrapper implementation representing the - * session for which this request was sent. - * - * Multiple Vaadin applications can be associated with a single session. - * - * @return Session - */ - public Session getSession(); - - /** - * Are the applications in this session running in a portlet or directly - * as servlets. - * - * @return true if in a portlet - */ - public boolean isRunningInPortlet(); - - /** - * Get the named HTTP or portlet request parameter. - * - * @see javax.servlet.ServletRequest#getParameter(String) - * @see javax.portlet.PortletRequest#getParameter(String) - * - * @param name - * @return - */ - public String getParameter(String name); - - /** - * Returns the length of the request content that can be read from the - * input stream returned by {@link #getInputStream()}. - * - * @return content length in bytes - */ - public int getContentLength(); - - /** - * Returns an input stream from which the request content can be read. - * The request content length can be obtained with - * {@link #getContentLength()} without reading the full stream contents. - * - * @return - * @throws IOException - */ - public InputStream getInputStream() throws IOException; - - /** - * Returns the request identifier that identifies the target Vaadin - * window for the request. - * - * @return String identifier for the request target window - */ - public String getRequestID(); - - /** - * @see javax.servlet.ServletRequest#getAttribute(String) - * @see javax.portlet.PortletRequest#getAttribute(String) - */ - public Object getAttribute(String name); - - /** - * @see javax.servlet.ServletRequest#setAttribute(String, Object) - * @see javax.portlet.PortletRequest#setAttribute(String, Object) - */ - public void setAttribute(String name, Object value); - - /** - * Gets the underlying request object. The request is typically either a - * {@link ServletRequest} or a {@link PortletRequest}. - * - * @return wrapped request object - */ - public Object getWrappedRequest(); + private static final RequestHandler APP_RESOURCE_HANDLER = new ApplicationResourceHandler(); - } - - /** - * Generic interface of a (HTTP or Portlet) response from the application. - * - * This is a wrapper interface that allows - * {@link AbstractCommunicationManager} to use a unified API. - * - * @see javax.servlet.ServletResponse - * @see javax.portlet.PortletResponse - * - * @author peholmst - */ - public interface Response { - - /** - * Gets the output stream to which the response can be written. - * - * @return - * @throws IOException - */ - public OutputStream getOutputStream() throws IOException; - - /** - * Sets the MIME content type for the response to be communicated to the - * browser. - * - * @param type - */ - public void setContentType(String type); - - /** - * Gets the wrapped response object, usually a class implementing either - * {@link ServletResponse} or {@link PortletResponse}. - * - * @return wrapped request object - */ - public Object getWrappedResponse(); - - } - - /** - * Generic wrapper interface for a (HTTP or Portlet) session. - * - * Several applications can be associated with a single session. - * - * TODO Document me! - * - * @see javax.servlet.http.HttpSession - * @see javax.portlet.PortletSession - * - * @author peholmst - */ - protected interface Session { - - public boolean isNew(); - - public Object getAttribute(String name); - - public void setAttribute(String name, Object o); - - public int getMaxInactiveInterval(); - - public Object getWrappedSession(); - - } + private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler(); /** * TODO Document me! * * @author peholmst */ - public interface Callback { - - public void criticalNotification(Request request, Response response, - String cap, String msg, String details, String outOfSyncURL) - throws IOException; - - public String getRequestPathInfo(Request request); - - public InputStream getThemeResourceAsStream(String themeName, - String resource) throws IOException; + public interface Callback extends Serializable { + public void criticalNotification(WrappedRequest request, + WrappedResponse response, String cap, String msg, + String details, String outOfSyncURL) throws IOException; } static class UploadInterruptedException extends Exception { @@ -280,33 +122,11 @@ public abstract class AbstractCommunicationManager implements private static final String WRITE_SECURITY_TOKEN_FLAG = "writeSecurityToken"; /* Variable records indexes */ - private static final int VAR_PID = 1; - private static final int VAR_NAME = 2; - private static final int VAR_TYPE = 3; - private static final int VAR_VALUE = 0; - - private static final char VTYPE_PAINTABLE = 'p'; - private static final char VTYPE_BOOLEAN = 'b'; - private static final char VTYPE_DOUBLE = 'd'; - private static final char VTYPE_FLOAT = 'f'; - private static final char VTYPE_LONG = 'l'; - private static final char VTYPE_INTEGER = 'i'; - private static final char VTYPE_STRING = 's'; - private static final char VTYPE_ARRAY = 'a'; - private static final char VTYPE_STRINGARRAY = 'c'; - private static final char VTYPE_MAP = 'm'; - - private static final char VAR_RECORD_SEPARATOR = '\u001e'; - - private static final char VAR_FIELD_SEPARATOR = '\u001f'; - public static final char VAR_BURST_SEPARATOR = '\u001d'; - public static final char VAR_ARRAYITEM_SEPARATOR = '\u001c'; - public static final char VAR_ESCAPE_CHARACTER = '\u001b'; - private final HashMap<String, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<String, OpenWindowCache>(); + private final HashMap<Integer, ClientCache> rootToClientCache = new HashMap<Integer, ClientCache>(); private static final int MAX_BUFFER_SIZE = 64 * 1024; @@ -315,20 +135,8 @@ public abstract class AbstractCommunicationManager implements private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts"; - private final ArrayList<Paintable> dirtyPaintables = new ArrayList<Paintable>(); - - private final HashMap<Paintable, String> paintableIdMap = new HashMap<Paintable, String>(); - - private final HashMap<String, Paintable> idPaintableMap = new HashMap<String, Paintable>(); - - private int idSequence = 0; - private final Application application; - // Note that this is only accessed from synchronized block and - // thus should be thread-safe. - private String closingWindowName = null; - private List<String> locales; private int pendingLocalesIndex; @@ -341,7 +149,7 @@ public abstract class AbstractCommunicationManager implements private int maxInactiveInterval; - private static int nextUnusedWindowSuffix = 1; + private Connector highlightedConnector; /** * TODO New constructor - document me! @@ -350,6 +158,9 @@ public abstract class AbstractCommunicationManager implements */ public AbstractCommunicationManager(Application application) { this.application = application; + application.addRequestHandler(getBootstrapHandler()); + application.addRequestHandler(APP_RESOURCE_HANDLER); + application.addRequestHandler(UNSUPPORTED_BROWSER_HANDLER); requireLocale(application.getLocale().toString()); } @@ -365,8 +176,6 @@ public abstract class AbstractCommunicationManager implements private static final String GET_PARAM_HIGHLIGHT_COMPONENT = "highlightComponent"; - private Paintable highLightedPaintable; - private static String readLine(InputStream stream) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); int readByte = stream.read(); @@ -390,9 +199,9 @@ public abstract class AbstractCommunicationManager implements * @param boundary * @throws IOException */ - protected void doHandleSimpleMultipartFileUpload(Request request, - Response response, StreamVariable streamVariable, - String variableName, VariableOwner owner, String boundary) + protected void doHandleSimpleMultipartFileUpload(WrappedRequest request, + WrappedResponse response, StreamVariable streamVariable, + String variableName, Connector owner, String boundary) throws IOException { // multipart parsing, supports only one file for request, but that is // fine for our current terminal @@ -489,9 +298,10 @@ public abstract class AbstractCommunicationManager implements * @param contentLength * @throws IOException */ - protected void doHandleXhrFilePost(Request request, Response response, - StreamVariable streamVariable, String variableName, - VariableOwner owner, int contentLength) throws IOException { + protected void doHandleXhrFilePost(WrappedRequest request, + WrappedResponse response, StreamVariable streamVariable, + String variableName, Connector owner, int contentLength) + throws IOException { // These are unknown in filexhr ATM, maybe add to Accept header that // is accessible in portlets @@ -628,17 +438,6 @@ public abstract class AbstractCommunicationManager implements } } - static void tryToCloseStream(InputStream in) { - try { - // try to close output stream (e.g. file handle) - if (in != null) { - in.close(); - } - } catch (IOException e1) { - // NOP - } - } - /** * Removes any possible path information from the filename and returns the * filename. Separators / and \\ are used. @@ -661,8 +460,8 @@ public abstract class AbstractCommunicationManager implements * @param response * @throws IOException */ - protected void sendUploadResponse(Request request, Response response) - throws IOException { + protected void sendUploadResponse(WrappedRequest request, + WrappedResponse response) throws IOException { response.setContentType("text/html"); final OutputStream out = response.getOutputStream(); final PrintWriter outWriter = new PrintWriter(new BufferedWriter( @@ -676,7 +475,7 @@ public abstract class AbstractCommunicationManager implements * Internally process a UIDL request from the client. * * This method calls - * {@link #handleVariables(Request, Response, Callback, Application, Window)} + * {@link #handleVariables(WrappedRequest, WrappedResponse, Callback, Application, Root)} * to process any changes to variables by the client and then repaints * affected components using {@link #paintAfterVariableChanges()}. * @@ -690,18 +489,18 @@ public abstract class AbstractCommunicationManager implements * @param request * @param response * @param callback - * @param window + * @param root * target window for the UIDL request, can be null if target not * found * @throws IOException * @throws InvalidUIDLSecurityKeyException */ - protected void doHandleUidlRequest(Request request, Response response, - Callback callback, Window window) throws IOException, - InvalidUIDLSecurityKeyException { + public void handleUidlRequest(WrappedRequest request, + WrappedResponse response, Callback callback, Root root) + throws IOException, InvalidUIDLSecurityKeyException { requestThemeName = request.getParameter("theme"); - maxInactiveInterval = request.getSession().getMaxInactiveInterval(); + maxInactiveInterval = request.getSessionMaxInactiveInterval(); // repaint requested or session has timed out and new one is created boolean repaintAll; final OutputStream out; @@ -718,8 +517,8 @@ public abstract class AbstractCommunicationManager implements if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) { String pid = request .getParameter(GET_PARAM_HIGHLIGHT_COMPONENT); - highLightedPaintable = idPaintableMap.get(pid); - highlightPaintable(highLightedPaintable); + highlightedConnector = root.getApplication().getConnector(pid); + highlightConnector(highlightedConnector); } } @@ -734,11 +533,10 @@ public abstract class AbstractCommunicationManager implements // Finds the window within the application if (application.isRunning()) { // Returns if no window found - if (window == null) { + if (root == null) { // This should not happen, no windows exists but // application is still open. - logger.warning("Could not get window for application with request ID " - + request.getRequestID()); + logger.warning("Could not get root for application"); return; } } else { @@ -748,8 +546,7 @@ public abstract class AbstractCommunicationManager implements } // Change all variables based on request parameters - if (!handleVariables(request, response, callback, application, - window)) { + if (!handleVariables(request, response, callback, application, root)) { // var inconsistency; the client is probably out-of-sync SystemMessages ci = null; @@ -780,27 +577,36 @@ public abstract class AbstractCommunicationManager implements } paintAfterVariableChanges(request, response, callback, repaintAll, - outWriter, window, analyzeLayouts); - - if (closingWindowName != null) { - currentlyOpenWindowsInClient.remove(closingWindowName); - closingWindowName = null; - } + outWriter, root, analyzeLayouts); + postPaint(root); } outWriter.close(); requestThemeName = null; } - protected void highlightPaintable(Paintable highLightedPaintable2) { + /** + * Method called after the paint phase while still being synchronized on the + * application + * + * @param root + * + */ + protected void postPaint(Root root) { + // Remove connectors that have been detached from the application during + // handling of the request + root.getApplication().cleanConnectorMap(); + } + + protected void highlightConnector(Connector highlightedConnector) { StringBuilder sb = new StringBuilder(); sb.append("*** Debug details of a component: *** \n"); sb.append("Type: "); - sb.append(highLightedPaintable2.getClass().getName()); - if (highLightedPaintable2 instanceof AbstractComponent) { - AbstractComponent component = (AbstractComponent) highLightedPaintable2; + sb.append(highlightedConnector.getClass().getName()); + if (highlightedConnector instanceof AbstractComponent) { + AbstractComponent component = (AbstractComponent) highlightedConnector; sb.append("\nId:"); - sb.append(paintableIdMap.get(component)); + sb.append(highlightedConnector.getConnectorId()); if (component.getCaption() != null) { sb.append("\nCaption:"); sb.append(component.getCaption()); @@ -864,14 +670,10 @@ public abstract class AbstractCommunicationManager implements * @throws PaintException * @throws IOException */ - private void paintAfterVariableChanges(Request request, Response response, - Callback callback, boolean repaintAll, final PrintWriter outWriter, - Window window, boolean analyzeLayouts) throws PaintException, - IOException { - - if (repaintAll) { - makeAllPaintablesDirty(window); - } + private void paintAfterVariableChanges(WrappedRequest request, + WrappedResponse response, Callback callback, boolean repaintAll, + final PrintWriter outWriter, Root root, boolean analyzeLayouts) + throws PaintException, IOException { // Removes application if it has stopped during variable changes if (!application.isRunning()) { @@ -886,180 +688,242 @@ public abstract class AbstractCommunicationManager implements .getAttribute(WRITE_SECURITY_TOKEN_FLAG); if (writeSecurityTokenFlag != null) { - String seckey = (String) request.getSession().getAttribute( - ApplicationConnection.UIDL_SECURITY_TOKEN_ID); - if (seckey == null) { - seckey = UUID.randomUUID().toString(); - request.getSession().setAttribute( - ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey); - } - outWriter.print("\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID - + "\":\""); - outWriter.print(seckey); - outWriter.print("\","); + outWriter.print(getSecurityKeyUIDL(request)); } - // If the browser-window has been closed - we do not need to paint it at - // all - if (window.getName().equals(closingWindowName)) { - outWriter.print("\"changes\":[]"); - } else { - // re-get window - may have been changed - Window newWindow = doGetApplicationWindow(request, callback, - application, window); - if (newWindow != window) { - window = newWindow; - repaintAll = true; - } - - writeUidlResponse(request, callback, repaintAll, outWriter, window, - analyzeLayouts); + writeUidlResponse(request, repaintAll, outWriter, root, analyzeLayouts); - } closeJsonMessage(outWriter); outWriter.close(); } - public void writeUidlResponse(Request request, Callback callback, - boolean repaintAll, final PrintWriter outWriter, Window window, - boolean analyzeLayouts) throws PaintException { - outWriter.print("\"changes\":["); - - ArrayList<Paintable> paintables = null; - - List<InvalidLayout> invalidComponentRelativeSizes = null; + /** + * Gets the security key (and generates one if needed) as UIDL. + * + * @param request + * @return the security key UIDL or "" if the feature is turned off + */ + public String getSecurityKeyUIDL(WrappedRequest request) { + final String seckey = getSecurityKey(request); + if (seckey != null) { + return "\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID + + "\":\"" + seckey + "\","; + } else { + return ""; + } + } - JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, - !repaintAll); - OpenWindowCache windowCache = currentlyOpenWindowsInClient.get(window - .getName()); - if (windowCache == null) { - windowCache = new OpenWindowCache(); - currentlyOpenWindowsInClient.put(window.getName(), windowCache); + /** + * Gets the security key (and generates one if needed). + * + * @param request + * @return the security key + */ + protected String getSecurityKey(WrappedRequest request) { + String seckey = null; + seckey = (String) request + .getSessionAttribute(ApplicationConnection.UIDL_SECURITY_TOKEN_ID); + if (seckey == null) { + seckey = UUID.randomUUID().toString(); + request.setSessionAttribute( + ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey); } + return seckey; + } + + @SuppressWarnings("unchecked") + public void writeUidlResponse(WrappedRequest request, boolean repaintAll, + final PrintWriter outWriter, Root root, boolean analyzeLayouts) + throws PaintException { + ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<ClientConnector>(); + Application application = root.getApplication(); // Paints components + DirtyConnectorTracker rootConnectorTracker = root + .getDirtyConnectorTracker(); + logger.log(Level.FINE, "* Creating response to client"); if (repaintAll) { - paintables = new ArrayList<Paintable>(); - paintables.add(window); + getClientCache(root).clear(); + rootConnectorTracker.markAllComponentsDirty(); // Reset sent locales locales = null; requireLocale(application.getLocale().toString()); + } - } else { - // remove detached components from paintableIdMap so they - // can be GC'ed - /* - * TODO figure out if we could move this beyond the painting phase, - * "respond as fast as possible, then do the cleanup". Beware of - * painting the dirty detatched components. - */ - for (Iterator<Paintable> it = paintableIdMap.keySet().iterator(); it - .hasNext();) { - Component p = (Component) it.next(); - if (p.getApplication() == null) { - unregisterPaintable(p); - // Take into account that some other component may have - // reused p's ID by now (this can happen when manually - // assigning IDs with setDebugId().) See #8090. - String pid = paintableIdMap.get(p); - if (idPaintableMap.get(pid) == p) { - idPaintableMap.remove(pid); - } - it.remove(); - dirtyPaintables.remove(p); + dirtyVisibleConnectors + .addAll(getDirtyVisibleComponents(rootConnectorTracker)); + + logger.log(Level.FINE, "Found " + dirtyVisibleConnectors.size() + + " dirty connectors to paint"); + for (ClientConnector connector : dirtyVisibleConnectors) { + if (connector instanceof Component) { + ((Component) connector).updateState(); + } + } + rootConnectorTracker.markAllComponentsClean(); + + outWriter.print("\"changes\":["); + + List<InvalidLayout> invalidComponentRelativeSizes = null; + + JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, + !repaintAll); + legacyPaint(paintTarget, dirtyVisibleConnectors); + + if (analyzeLayouts) { + invalidComponentRelativeSizes = ComponentSizeValidator + .validateComponentRelativeSizes(root.getContent(), null, + null); + + // Also check any existing subwindows + if (root.getWindows() != null) { + for (Window subWindow : root.getWindows()) { + invalidComponentRelativeSizes = ComponentSizeValidator + .validateComponentRelativeSizes( + subWindow.getContent(), + invalidComponentRelativeSizes, null); } } - paintables = getDirtyVisibleComponents(window); - } - if (paintables != null) { - - // We need to avoid painting children before parent. - // This is ensured by ordering list by depth in component - // tree - Collections.sort(paintables, new Comparator<Paintable>() { - public int compare(Paintable o1, Paintable o2) { - Component c1 = (Component) o1; - Component c2 = (Component) o2; - int d1 = 0; - while (c1.getParent() != null) { - d1++; - c1 = c1.getParent(); - } - int d2 = 0; - while (c2.getParent() != null) { - d2++; - c2 = c2.getParent(); - } - if (d1 < d2) { - return -1; - } - if (d1 > d2) { - return 1; - } - return 0; + } + + paintTarget.close(); + outWriter.print("], "); // close changes + + // send shared state to client + + // for now, send the complete state of all modified and new + // components + + // Ideally, all this would be sent before "changes", but that causes + // complications with legacy components that create sub-components + // in their paint phase. Nevertheless, this will be processed on the + // client after component creation but before legacy UIDL + // processing. + JSONObject sharedStates = new JSONObject(); + for (Connector connector : dirtyVisibleConnectors) { + SharedState state = connector.getState(); + if (null != state) { + // encode and send shared state + try { + // FIXME Use declared type + JSONArray stateJsonArray = JsonCodec.encode(state, + state.getClass(), application); + sharedStates + .put(connector.getConnectorId(), stateJsonArray); + } catch (JSONException e) { + throw new PaintException( + "Failed to serialize shared state for connector " + + connector.getClass().getName() + " (" + + connector.getConnectorId() + "): " + + e.getMessage()); } - }); + } + } + outWriter.print("\"state\":"); + outWriter.append(sharedStates.toString()); + outWriter.print(", "); // close states - for (final Iterator<Paintable> i = paintables.iterator(); i - .hasNext();) { - final Paintable p = i.next(); + // TODO This should be optimized. The type only needs to be + // sent once for each connector id + on refresh. Use the same cache as + // widget mapping - // TODO CLEAN - if (p instanceof Window) { - final Window w = (Window) p; - if (w.getTerminal() == null) { - w.setTerminal(application.getMainWindow().getTerminal()); + JSONObject connectorTypes = new JSONObject(); + for (ClientConnector connector : dirtyVisibleConnectors) { + String connectorType = paintTarget.getTag(connector); + try { + connectorTypes.put(connector.getConnectorId(), connectorType); + } catch (JSONException e) { + throw new PaintException( + "Failed to send connector type for connector " + + connector.getConnectorId() + ": " + + e.getMessage()); + } + } + outWriter.print("\"types\":"); + outWriter.append(connectorTypes.toString()); + outWriter.print(", "); // close states + + // Send update hierarchy information to the client. + + // This could be optimized aswell to send only info if hierarchy has + // actually changed. Much like with the shared state. Note though + // that an empty hierarchy is information aswell (e.g. change from 1 + // child to 0 children) + + outWriter.print("\"hierarchy\":"); + + JSONObject hierarchyInfo = new JSONObject(); + for (Connector connector : dirtyVisibleConnectors) { + if (connector instanceof HasComponents) { + HasComponents parent = (HasComponents) connector; + String parentConnectorId = parent.getConnectorId(); + JSONArray children = new JSONArray(); + + for (Component child : getChildComponents(parent)) { + if (isVisible(child)) { + children.put(child.getConnectorId()); } } - /* - * This does not seem to happen in tk5, but remember this case: - * else if (p instanceof Component) { if (((Component) - * p).getParent() == null || ((Component) p).getApplication() == - * null) { // Component requested repaint, but is no // longer - * attached: skip paintablePainted(p); continue; } } - */ + try { + hierarchyInfo.put(parentConnectorId, children); + } catch (JSONException e) { + throw new PaintException( + "Failed to send hierarchy information about " + + parentConnectorId + " to the client: " + + e.getMessage()); + } + } + } + outWriter.append(hierarchyInfo.toString()); + outWriter.print(", "); // close hierarchy - // TODO we may still get changes that have been - // rendered already (changes with only cached flag) - if (paintTarget.needsToBePainted(p)) { - paintTarget.startTag("change"); - paintTarget.addAttribute("format", "uidl"); - final String pid = getPaintableId(p); - paintTarget.addAttribute("pid", pid); + // send server to client RPC calls for components in the root, in call + // order - p.paint(paintTarget); + // collect RPC calls from components in the root in the order in + // which they were performed, remove the calls from components - paintTarget.endTag("change"); - } - paintablePainted(p); + LinkedList<ClientConnector> rpcPendingQueue = new LinkedList<ClientConnector>( + dirtyVisibleConnectors); + List<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(dirtyVisibleConnectors); - if (analyzeLayouts) { - Window w = (Window) p; - invalidComponentRelativeSizes = ComponentSizeValidator - .validateComponentRelativeSizes(w.getContent(), - null, null); - - // Also check any existing subwindows - if (w.getChildWindows() != null) { - for (Window subWindow : w.getChildWindows()) { - invalidComponentRelativeSizes = ComponentSizeValidator - .validateComponentRelativeSizes( - subWindow.getContent(), - invalidComponentRelativeSizes, null); - } - } + JSONArray rpcCalls = new JSONArray(); + for (ClientMethodInvocation invocation : pendingInvocations) { + // add invocation to rpcCalls + try { + JSONArray invocationJson = new JSONArray(); + invocationJson.put(invocation.getConnector().getConnectorId()); + invocationJson.put(invocation.getInterfaceName()); + invocationJson.put(invocation.getMethodName()); + JSONArray paramJson = new JSONArray(); + for (int i = 0; i < invocation.getParameterTypes().length; ++i) { + paramJson.put(JsonCodec.encode( + invocation.getParameters()[i], + invocation.getParameterTypes()[i], application)); } + invocationJson.put(paramJson); + rpcCalls.put(invocationJson); + } catch (JSONException e) { + throw new PaintException( + "Failed to serialize RPC method call parameters for connector " + + invocation.getConnector().getConnectorId() + + " method " + invocation.getInterfaceName() + + "." + invocation.getMethodName() + ": " + + e.getMessage()); } + } - paintTarget.close(); - outWriter.print("]"); // close changes + if (rpcCalls.length() > 0) { + outWriter.print("\"rpc\" : "); + outWriter.append(rpcCalls.toString()); + outWriter.print(", "); // close rpc + } - outWriter.print(", \"meta\" : {"); + outWriter.print("\"meta\" : {"); boolean metaOpen = false; if (repaintAll) { @@ -1081,11 +945,11 @@ public abstract class AbstractCommunicationManager implements } outWriter.write("]"); } - if (highLightedPaintable != null) { + if (highlightedConnector != null) { outWriter.write(", \"hl\":\""); - outWriter.write(paintableIdMap.get(highLightedPaintable)); + outWriter.write(highlightedConnector.getConnectorId()); outWriter.write("\""); - highLightedPaintable = null; + highlightedConnector = null; } } @@ -1140,8 +1004,7 @@ public abstract class AbstractCommunicationManager implements final String resource = (String) i.next(); InputStream is = null; try { - is = callback.getThemeResourceAsStream(getTheme(window), - resource); + is = getThemeResourceAsStream(root, getTheme(root), resource); } catch (final Exception e) { // FIXME: Handle exception logger.log(Level.FINER, "Failed to get theme resource stream.", @@ -1175,11 +1038,13 @@ public abstract class AbstractCommunicationManager implements } outWriter.print("}"); - Collection<Class<? extends Paintable>> usedPaintableTypes = paintTarget - .getUsedPaintableTypes(); + Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget + .getUsedClientConnectors(); boolean typeMappingsOpen = false; - for (Class<? extends Paintable> class1 : usedPaintableTypes) { - if (windowCache.cache(class1)) { + ClientCache clientCache = getClientCache(root); + + for (Class<? extends ClientConnector> class1 : usedClientConnectors) { + if (clientCache.cache(class1)) { // client does not know the mapping key for this type, send // mapping to client if (!typeMappingsOpen) { @@ -1199,6 +1064,32 @@ public abstract class AbstractCommunicationManager implements outWriter.print(" }"); } + boolean typeInheritanceMapOpen = false; + if (typeMappingsOpen) { + // send the whole type inheritance map if any new mappings + for (Class<? extends ClientConnector> class1 : usedClientConnectors) { + if (!ClientConnector.class.isAssignableFrom(class1 + .getSuperclass())) { + continue; + } + if (!typeInheritanceMapOpen) { + typeInheritanceMapOpen = true; + outWriter.print(", \"typeInheritanceMap\" : { "); + } else { + outWriter.print(" , "); + } + outWriter.print("\""); + outWriter.print(getTagForType(class1)); + outWriter.print("\" : "); + outWriter + .print(getTagForType((Class<? extends ClientConnector>) class1 + .getSuperclass())); + } + if (typeInheritanceMapOpen) { + outWriter.print(" }"); + } + } + // add any pending locale definitions requested by the client printLocaleDeclarations(outWriter); @@ -1213,7 +1104,7 @@ public abstract class AbstractCommunicationManager implements * Adds the performance timing data used by TestBench 3 to the UIDL * response. */ - private void writePerformanceDataForTestBench(final Request request, + private void writePerformanceDataForTestBench(final WrappedRequest request, final PrintWriter outWriter) { Long totalTime = (Long) request.getAttribute("TOTAL"); Long lastRequestTime = (Long) request.getAttribute("LASTREQUEST"); @@ -1221,12 +1112,174 @@ public abstract class AbstractCommunicationManager implements lastRequestTime)); } + private void legacyPaint(PaintTarget paintTarget, + ArrayList<ClientConnector> dirtyVisibleConnectors) + throws PaintException { + List<Vaadin6Component> legacyComponents = new ArrayList<Vaadin6Component>(); + for (Connector connector : dirtyVisibleConnectors) { + // All Components that want to use paintContent must implement + // Vaadin6Component + if (connector instanceof Vaadin6Component) { + legacyComponents.add((Vaadin6Component) connector); + } + } + sortByHierarchy((List) legacyComponents); + for (Vaadin6Component c : legacyComponents) { + logger.fine("Painting Vaadin6Component " + c.getClass().getName() + + "@" + Integer.toHexString(c.hashCode())); + paintTarget.startTag("change"); + final String pid = c.getConnectorId(); + paintTarget.addAttribute("pid", pid); + LegacyPaint.paint(c, paintTarget); + paintTarget.endTag("change"); + } + + } + + private void sortByHierarchy(List<Component> paintables) { + // Vaadin 6 requires parents to be painted before children as component + // containers rely on that their updateFromUIDL method has been called + // before children start calling e.g. updateCaption + Collections.sort(paintables, new Comparator<Component>() { + public int compare(Component c1, Component c2) { + int depth1 = 0; + while (c1.getParent() != null) { + depth1++; + c1 = c1.getParent(); + } + int depth2 = 0; + while (c2.getParent() != null) { + depth2++; + c2 = c2.getParent(); + } + if (depth1 < depth2) { + return -1; + } + if (depth1 > depth2) { + return 1; + } + return 0; + } + }); + + } + + private ClientCache getClientCache(Root root) { + Integer rootId = Integer.valueOf(root.getRootId()); + ClientCache cache = rootToClientCache.get(rootId); + if (cache == null) { + cache = new ClientCache(); + rootToClientCache.put(rootId, cache); + } + return cache; + } + + /** + * Checks if the component is visible in context, i.e. returns false if the + * child is hidden, the parent is hidden or the parent says the child should + * not be rendered (using + * {@link HasComponents#isComponentVisible(Component)} + * + * @param child + * The child to check + * @return true if the child is visible to the client, false otherwise + */ + static boolean isVisible(Component child) { + HasComponents parent = child.getParent(); + if (parent == null || !child.isVisible()) { + return child.isVisible(); + } + + return parent.isComponentVisible(child) && isVisible(parent); + } + + private static class NullIterator<E> implements Iterator<E> { + + public boolean hasNext() { + return false; + } + + public E next() { + return null; + } + + public void remove() { + } + + } + + public static Iterable<Component> getChildComponents(HasComponents cc) { + // TODO This must be moved to Root/Panel + if (cc instanceof Root) { + Root root = (Root) cc; + List<Component> children = new ArrayList<Component>(); + if (root.getContent() != null) { + children.add(root.getContent()); + } + for (Window w : root.getWindows()) { + children.add(w); + } + return children; + } else if (cc instanceof Panel) { + // This is so wrong.. (#2924) + if (((Panel) cc).getContent() == null) { + return Collections.emptyList(); + } else { + return Collections.singleton((Component) ((Panel) cc) + .getContent()); + } + } + return cc; + } + + /** + * Collects all pending RPC calls from listed {@link ClientConnector}s and + * clears their RPC queues. + * + * @param rpcPendingQueue + * list of {@link ClientConnector} of interest + * @return ordered list of pending RPC calls + */ + private List<ClientMethodInvocation> collectPendingRpcCalls( + List<ClientConnector> rpcPendingQueue) { + List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); + for (ClientConnector connector : rpcPendingQueue) { + List<ClientMethodInvocation> paintablePendingRpc = connector + .retrievePendingRpcCalls(); + if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) { + List<ClientMethodInvocation> oldPendingRpc = pendingInvocations; + int totalCalls = pendingInvocations.size() + + paintablePendingRpc.size(); + pendingInvocations = new ArrayList<ClientMethodInvocation>( + totalCalls); + + // merge two ordered comparable lists + for (int destIndex = 0, oldIndex = 0, paintableIndex = 0; destIndex < totalCalls; destIndex++) { + if (paintableIndex >= paintablePendingRpc.size() + || (oldIndex < oldPendingRpc.size() && ((Comparable<ClientMethodInvocation>) oldPendingRpc + .get(oldIndex)) + .compareTo(paintablePendingRpc + .get(paintableIndex)) <= 0)) { + pendingInvocations.add(oldPendingRpc.get(oldIndex++)); + } else { + pendingInvocations.add(paintablePendingRpc + .get(paintableIndex++)); + } + } + } + } + return pendingInvocations; + } + + protected abstract InputStream getThemeResourceAsStream(Root root, + String themeName, String resource); + private int getTimeoutInterval() { return maxInactiveInterval; } - private String getTheme(Window window) { - String themeName = window.getTheme(); + private String getTheme(Root root) { + String themeName = root.getApplication().getThemeForRoot(root); String requestThemeName = getRequestTheme(); if (requestThemeName != null) { @@ -1242,32 +1295,16 @@ public abstract class AbstractCommunicationManager implements return requestThemeName; } - public void makeAllPaintablesDirty(Window window) { - // If repaint is requested, clean all ids in this root window - for (final Iterator<String> it = idPaintableMap.keySet().iterator(); it - .hasNext();) { - final Component c = (Component) idPaintableMap.get(it.next()); - if (isChildOf(window, c)) { - it.remove(); - paintableIdMap.remove(c); - } - } - // clean WindowCache - OpenWindowCache openWindowCache = currentlyOpenWindowsInClient - .get(window.getName()); - if (openWindowCache != null) { - openWindowCache.clear(); - } - } - /** - * Called when communication manager stops listening for repaints for given - * component. + * Returns false if the cross site request forgery protection is turned off. * - * @param p + * @param application + * @return false if the XSRF is turned off, true otherwise */ - protected void unregisterPaintable(Component p) { - p.removeListener(this); + public boolean isXSRFEnabled(Application application) { + return !"true" + .equals(application + .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); } /** @@ -1279,9 +1316,10 @@ public abstract class AbstractCommunicationManager implements * * @return true if successful, false if there was an inconsistency */ - private boolean handleVariables(Request request, Response response, - Callback callback, Application application2, Window window) - throws IOException, InvalidUIDLSecurityKeyException { + private boolean handleVariables(WrappedRequest request, + WrappedResponse response, Callback callback, + Application application2, Root root) throws IOException, + InvalidUIDLSecurityKeyException { boolean success = true; String changes = getRequestPayload(request); @@ -1293,9 +1331,7 @@ public abstract class AbstractCommunicationManager implements // Security: double cookie submission pattern unless disabled by // property - if (!"true" - .equals(application2 - .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION))) { + if (isXSRFEnabled(application2)) { if (bursts.length == 1 && "init".equals(bursts[0])) { // init request; don't handle any variables, key sent in // response. @@ -1304,8 +1340,8 @@ public abstract class AbstractCommunicationManager implements } else { // ApplicationServlet has stored the security token in the // session; check that it matched the one sent in the UIDL - String sessId = (String) request.getSession().getAttribute( - ApplicationConnection.UIDL_SECURITY_TOKEN_ID); + String sessId = (String) request + .getSessionAttribute(ApplicationConnection.UIDL_SECURITY_TOKEN_ID); if (sessId == null || !sessId.equals(bursts[0])) { throw new InvalidUIDLSecurityKeyException( @@ -1316,9 +1352,9 @@ public abstract class AbstractCommunicationManager implements } for (int bi = 1; bi < bursts.length; bi++) { - final String burst = bursts[bi]; - success = handleVariableBurst(request, application2, success, - burst); + // unescape any encoded separator characters in the burst + final String burst = unescapeBurst(bursts[bi]); + success &= handleBurst(request, application2, burst); // In case that there were multiple bursts, we know that this is // a special synchronous case for closing window. Thus we are @@ -1333,7 +1369,7 @@ public abstract class AbstractCommunicationManager implements new CharArrayWriter()); paintAfterVariableChanges(request, response, callback, - true, outWriter, window, false); + true, outWriter, root, false); } @@ -1345,127 +1381,250 @@ public abstract class AbstractCommunicationManager implements * we don't have the required logic implemented on the server side. E.g. * a component is removed in a previous burst. */ - return success || closingWindowName != null; + return success; } - public boolean handleVariableBurst(Object source, Application app, - boolean success, final String burst) { - // extract variables to two dim string array - final String[] tmp = burst.split(String.valueOf(VAR_RECORD_SEPARATOR)); - final String[][] variableRecords = new String[tmp.length][4]; - for (int i = 0; i < tmp.length; i++) { - variableRecords[i] = tmp[i].split(String - .valueOf(VAR_FIELD_SEPARATOR)); - } - - for (int i = 0; i < variableRecords.length; i++) { - String[] variable = variableRecords[i]; - String[] nextVariable = null; - if (i + 1 < variableRecords.length) { - nextVariable = variableRecords[i + 1]; + /** + * Processes a message burst received from the client. + * + * A burst can contain any number of RPC calls, including legacy variable + * change calls that are processed separately. + * + * Consecutive changes to the value of the same variable are combined and + * changeVariables() is only called once for them. This preserves the Vaadin + * 6 semantics for components and add-ons that do not use Vaadin 7 RPC + * directly. + * + * @param source + * @param app + * application receiving the burst + * @param burst + * the content of the burst as a String to be parsed + * @return true if the processing of the burst was successful and there were + * no messages to non-existent components + */ + public boolean handleBurst(Object source, Application app, + final String burst) { + boolean success = true; + try { + Set<Connector> enabledConnectors = new HashSet<Connector>(); + + List<MethodInvocation> invocations = parseInvocations(burst); + for (MethodInvocation invocation : invocations) { + final ClientConnector connector = getConnector(app, + invocation.getConnectorId()); + + if (connector != null && connector.isConnectorEnabled()) { + enabledConnectors.add(connector); + } } - final VariableOwner owner = getVariableOwner(variable[VAR_PID]); - if (owner != null && owner.isEnabled()) { - Map<String, Object> m; - if (nextVariable != null - && variable[VAR_PID].equals(nextVariable[VAR_PID])) { - // we have more than one value changes in row for - // one variable owner, collect them in HashMap - m = new HashMap<String, Object>(); - m.put(variable[VAR_NAME], - convertVariableValue(variable[VAR_TYPE].charAt(0), - variable[VAR_VALUE])); - } else { - // use optimized single value map - m = Collections.singletonMap( - variable[VAR_NAME], - convertVariableValue(variable[VAR_TYPE].charAt(0), - variable[VAR_VALUE])); + for (int i = 0; i < invocations.size(); i++) { + MethodInvocation invocation = invocations.get(i); + + final ClientConnector connector = getConnector(app, + invocation.getConnectorId()); + + if (connector == null) { + logger.log( + Level.WARNING, + "RPC call to " + invocation.getInterfaceName() + + "." + invocation.getMethodName() + + " received for connector " + + invocation.getConnectorId() + + " but no such connector could be found"); + continue; } - // collect following variable changes for this owner - while (nextVariable != null - && variable[VAR_PID].equals(nextVariable[VAR_PID])) { - i++; - variable = nextVariable; - if (i + 1 < variableRecords.length) { - nextVariable = variableRecords[i + 1]; - } else { - nextVariable = null; - } - m.put(variable[VAR_NAME], - convertVariableValue(variable[VAR_TYPE].charAt(0), - variable[VAR_VALUE])); - } - try { - changeVariables(source, owner, m); - - // Special-case of closing browser-level windows: - // track browser-windows currently open in client - if (owner instanceof Window - && ((Window) owner).getParent() == null) { - final Boolean close = (Boolean) m.get("close"); - if (close != null && close.booleanValue()) { - closingWindowName = ((Window) owner).getName(); + if (!enabledConnectors.contains(connector)) { + + if (invocation instanceof LegacyChangeVariablesInvocation) { + LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation; + // TODO convert window close to a separate RPC call and + // handle above - not a variable change + + // Handle special case where window-close is called + // after the window has been removed from the + // application or the application has closed + Map<String, Object> changes = legacyInvocation + .getVariableChanges(); + if (changes.size() == 1 && changes.containsKey("close") + && Boolean.TRUE.equals(changes.get("close"))) { + // Silently ignore this + continue; } } - } catch (Exception e) { - Component errorComponent = null; - if (owner instanceof Component) { - errorComponent = (Component) owner; - } else if (owner instanceof DragAndDropService) { - if (m.get("dhowner") instanceof Component) { - errorComponent = (Component) m.get("dhowner"); + + // Connector is disabled, log a warning and move to the next + String msg = "Ignoring RPC call for disabled connector " + + connector.getClass().getName(); + if (connector instanceof Component) { + String caption = ((Component) connector).getCaption(); + if (caption != null) { + msg += ", caption=" + caption; } } - - handleChangeVariablesError(app, errorComponent, e, m); - } - } else { - - // Handle special case where window-close is called - // after the window has been removed from the - // application or the application has closed - if ("close".equals(variable[VAR_NAME]) - && "true".equals(variable[VAR_VALUE])) { - // Silently ignore this + logger.warning(msg); continue; } - // Ignore variable change - String msg = "Warning: Ignoring variable change for "; - if (owner != null) { - msg += "disabled component " + owner.getClass(); - String caption = ((Component) owner).getCaption(); - if (caption != null) { - msg += ", caption=" + caption; - } + if (invocation instanceof ServerRpcMethodInvocation) { + ServerRpcManager.applyInvocation(connector, + (ServerRpcMethodInvocation) invocation); } else { - msg += "non-existent component, VAR_PID=" - + variable[VAR_PID]; - success = false; + + // All code below is for legacy variable changes + LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation; + Map<String, Object> changes = legacyInvocation + .getVariableChanges(); + try { + changeVariables(source, (VariableOwner) connector, + changes); + } catch (Exception e) { + Component errorComponent = null; + if (connector instanceof Component) { + errorComponent = (Component) connector; + } else if (connector instanceof DragAndDropService) { + Object dropHandlerOwner = changes.get("dhowner"); + if (dropHandlerOwner instanceof Component) { + errorComponent = (Component) dropHandlerOwner; + } + } + handleChangeVariablesError(app, errorComponent, e, + changes); + + } } - logger.warning(msg); - continue; } + + } catch (JSONException e) { + logger.warning("Unable to parse RPC call from the client: " + + e.getMessage()); + // TODO or return success = false? + throw new RuntimeException(e); } + return success; } + /** + * Parse a message burst from the client into a list of MethodInvocation + * instances. + * + * @param burst + * message string (JSON) + * @return list of MethodInvocation to perform + * @throws JSONException + */ + private List<MethodInvocation> parseInvocations(final String burst) + throws JSONException { + JSONArray invocationsJson = new JSONArray(burst); + + ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>(); + + MethodInvocation previousInvocation = null; + // parse JSON to MethodInvocations + for (int i = 0; i < invocationsJson.length(); ++i) { + + JSONArray invocationJson = invocationsJson.getJSONArray(i); + + MethodInvocation invocation = parseInvocation(invocationJson, + previousInvocation); + if (invocation != null) { + // Can be null iff the invocation was a legacy invocation and it + // was merged with the previous one + invocations.add(invocation); + previousInvocation = invocation; + } + } + return invocations; + } + + private MethodInvocation parseInvocation(JSONArray invocationJson, + MethodInvocation previousInvocation) throws JSONException { + String connectorId = invocationJson.getString(0); + String interfaceName = invocationJson.getString(1); + String methodName = invocationJson.getString(2); + + JSONArray parametersJson = invocationJson.getJSONArray(3); + + if (LegacyChangeVariablesInvocation.isLegacyVariableChange( + interfaceName, methodName)) { + if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) { + previousInvocation = null; + } + + return parseLegacyChangeVariablesInvocation(connectorId, + interfaceName, methodName, + (LegacyChangeVariablesInvocation) previousInvocation, + parametersJson); + } else { + return parseServerRpcInvocation(connectorId, interfaceName, + methodName, parametersJson); + } + + } + + private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation( + String connectorId, String interfaceName, String methodName, + LegacyChangeVariablesInvocation previousInvocation, + JSONArray parametersJson) throws JSONException { + if (parametersJson.length() != 2) { + throw new JSONException( + "Invalid parameters in legacy change variables call. Expected 2, was " + + parametersJson.length()); + } + String variableName = (String) JsonCodec + .decodeInternalType(String.class, true, + parametersJson.getJSONArray(0), application); + Object value = JsonCodec.decodeInternalType( + parametersJson.getJSONArray(1), application); + + if (previousInvocation != null + && previousInvocation.getConnectorId().equals(connectorId)) { + previousInvocation.setVariableChange(variableName, value); + return null; + } else { + return new LegacyChangeVariablesInvocation(connectorId, + variableName, value); + } + } + + private ServerRpcMethodInvocation parseServerRpcInvocation( + String connectorId, String interfaceName, String methodName, + JSONArray parametersJson) throws JSONException { + ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation( + connectorId, interfaceName, methodName, parametersJson.length()); + + Object[] parameters = new Object[parametersJson.length()]; + Type[] declaredRpcMethodParameterTypes = invocation.getMethod() + .getGenericParameterTypes(); + + for (int j = 0; j < parametersJson.length(); ++j) { + JSONArray parameterJson = parametersJson.getJSONArray(j); + Type parameterType = declaredRpcMethodParameterTypes[j]; + parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType, + parameterJson, application); + } + invocation.setParameters(parameters); + return invocation; + } + protected void changeVariables(Object source, final VariableOwner owner, Map<String, Object> m) { owner.changeVariables(source, m); } - protected VariableOwner getVariableOwner(String string) { - VariableOwner owner = (VariableOwner) idPaintableMap.get(string); - if (owner == null && string.startsWith("DD")) { + protected ClientConnector getConnector(Application app, String connectorId) { + ClientConnector c = app.getConnector(connectorId); + if (c == null + && connectorId.equals(getDragAndDropService().getConnectorId())) { return getDragAndDropService(); } - return owner; + + return c; } - private VariableOwner getDragAndDropService() { + private DragAndDropService getDragAndDropService() { if (dragAndDropService == null) { dragAndDropService = new DragAndDropService(this); } @@ -1480,7 +1639,8 @@ public abstract class AbstractCommunicationManager implements * @return * @throws IOException */ - protected String getRequestPayload(Request request) throws IOException { + protected String getRequestPayload(WrappedRequest request) + throws IOException { int requestLength = request.getContentLength(); if (requestLength == 0) { @@ -1544,7 +1704,7 @@ public abstract class AbstractCommunicationManager implements if (owner instanceof AbstractField) { try { - handled = ((AbstractField) owner).handleError(errorEvent); + handled = ((AbstractField<?>) owner).handleError(errorEvent); } catch (Exception handlerException) { /* * If there is an error in the component error handler we pass @@ -1563,111 +1723,15 @@ public abstract class AbstractCommunicationManager implements } - private Object convertVariableValue(char variableType, String strValue) { - Object val = null; - switch (variableType) { - case VTYPE_ARRAY: - val = convertArray(strValue); - break; - case VTYPE_MAP: - val = convertMap(strValue); - break; - case VTYPE_STRINGARRAY: - val = convertStringArray(strValue); - break; - case VTYPE_STRING: - // decode encoded separators - val = decodeVariableValue(strValue); - break; - case VTYPE_INTEGER: - val = Integer.valueOf(strValue); - break; - case VTYPE_LONG: - val = Long.valueOf(strValue); - break; - case VTYPE_FLOAT: - val = Float.valueOf(strValue); - break; - case VTYPE_DOUBLE: - val = Double.valueOf(strValue); - break; - case VTYPE_BOOLEAN: - val = Boolean.valueOf(strValue); - break; - case VTYPE_PAINTABLE: - val = idPaintableMap.get(strValue); - break; - } - - return val; - } - - private Object convertMap(String strValue) { - String[] parts = strValue - .split(String.valueOf(VAR_ARRAYITEM_SEPARATOR)); - HashMap<String, Object> map = new HashMap<String, Object>(); - for (int i = 0; i < parts.length; i += 2) { - String key = parts[i]; - if (key.length() > 0) { - char variabletype = key.charAt(0); - // decode encoded separators - String decodedValue = decodeVariableValue(parts[i + 1]); - String decodedKey = decodeVariableValue(key.substring(1)); - Object value = convertVariableValue(variabletype, decodedValue); - map.put(decodedKey, value); - } - } - return map; - } - - private String[] convertStringArray(String strValue) { - // need to return delimiters and filter them out; otherwise empty - // strings are lost - // an extra empty delimiter at the end is automatically eliminated - final String arrayItemSeparator = String - .valueOf(VAR_ARRAYITEM_SEPARATOR); - StringTokenizer tokenizer = new StringTokenizer(strValue, - arrayItemSeparator, true); - List<String> tokens = new ArrayList<String>(); - String prevToken = arrayItemSeparator; - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - if (!arrayItemSeparator.equals(token)) { - // decode encoded separators - tokens.add(decodeVariableValue(token)); - } else if (arrayItemSeparator.equals(prevToken)) { - tokens.add(""); - } - prevToken = token; - } - return tokens.toArray(new String[tokens.size()]); - } - - private Object convertArray(String strValue) { - String[] val = strValue.split(String.valueOf(VAR_ARRAYITEM_SEPARATOR)); - if (val.length == 0 || (val.length == 1 && val[0].length() == 0)) { - return new Object[0]; - } - Object[] values = new Object[val.length]; - for (int i = 0; i < values.length; i++) { - String string = val[i]; - // first char of string is type - char variableType = string.charAt(0); - values[i] = convertVariableValue(variableType, string.substring(1)); - } - return values; - } - /** - * Decode encoded burst, record, field and array item separator characters - * in a variable value String received from the client. This protects from - * separator injection attacks. + * Unescape encoded burst separator characters in a burst received from the + * client. This protects from separator injection attacks. * * @param encodedValue * to decode * @return decoded value */ - protected String decodeVariableValue(String encodedValue) { + protected String unescapeBurst(String encodedValue) { final StringBuilder result = new StringBuilder(); final StringCharacterIterator iterator = new StringCharacterIterator( encodedValue); @@ -1681,9 +1745,6 @@ public abstract class AbstractCommunicationManager implements result.append(VAR_ESCAPE_CHARACTER); break; case VAR_BURST_SEPARATOR + 0x30: - case VAR_RECORD_SEPARATOR + 0x30: - case VAR_FIELD_SEPARATOR + 0x30: - case VAR_ARRAYITEM_SEPARATOR + 0x30: // +0x30 makes these letters for easier reading result.append((char) (character - 0x30)); break; @@ -1843,108 +1904,6 @@ public abstract class AbstractCommunicationManager implements } /** - * TODO New method - document me! - * - * @param request - * @param callback - * @param application - * @param assumedWindow - * @return - */ - protected Window doGetApplicationWindow(Request request, Callback callback, - Application application, Window assumedWindow) { - - Window window = null; - - // If the client knows which window to use, use it if possible - String windowClientRequestedName = request.getParameter("windowName"); - - if (assumedWindow != null - && application.getWindows().contains(assumedWindow)) { - windowClientRequestedName = assumedWindow.getName(); - } - if (windowClientRequestedName != null) { - window = application.getWindow(windowClientRequestedName); - if (window != null) { - return window; - } - } - - // If client does not know what window it wants - if (window == null && !request.isRunningInPortlet()) { - // This is only supported if the application is running inside a - // servlet - - // Get the path from URL - String path = callback.getRequestPathInfo(request); - - /* - * If the path is specified, create name from it. - * - * An exception is if UIDL request have come this far. This happens - * if main window is refreshed. In that case we always return main - * window (infamous hacky support for refreshes if only main window - * is used). However we are not returning with main window here (we - * will later if things work right), because the code is so cryptic - * that nobody really knows what it does. - */ - boolean pathMayContainWindowName = path != null - && path.length() > 0 && !path.equals("/"); - if (pathMayContainWindowName) { - boolean uidlRequest = path.startsWith("/UIDL"); - if (!uidlRequest) { - String windowUrlName = null; - if (path.charAt(0) == '/') { - path = path.substring(1); - } - final int index = path.indexOf('/'); - if (index < 0) { - windowUrlName = path; - path = ""; - } else { - windowUrlName = path.substring(0, index); - path = path.substring(index + 1); - } - - window = application.getWindow(windowUrlName); - } - } - - } - - // By default, use mainwindow - if (window == null) { - window = application.getMainWindow(); - // Return null if no main window was found - if (window == null) { - return null; - } - } - - // If the requested window is already open, resolve conflict - if (currentlyOpenWindowsInClient.containsKey(window.getName())) { - String newWindowName = window.getName(); - - synchronized (AbstractCommunicationManager.class) { - while (currentlyOpenWindowsInClient.containsKey(newWindowName)) { - newWindowName = window.getName() + "_" - + nextUnusedWindowSuffix++; - } - } - - window = application.getWindow(newWindowName); - - // If everything else fails, use main window even in case of - // conflicts - if (window == null) { - window = application.getMainWindow(); - } - } - - return window; - } - - /** * Ends the Application. * * The browser is redirected to the Application logout URL set with @@ -1960,8 +1919,9 @@ public abstract class AbstractCommunicationManager implements * @throws IOException * if the writing failed due to input/output error. */ - private void endApplication(Request request, Response response, - Application application) throws IOException { + private void endApplication(WrappedRequest request, + WrappedResponse response, Application application) + throws IOException { String logoutUrl = application.getLogoutURL(); if (logoutUrl == null) { @@ -1992,7 +1952,8 @@ public abstract class AbstractCommunicationManager implements * @param outWriter * @param response */ - protected void openJsonMessage(PrintWriter outWriter, Response response) { + protected void openJsonMessage(PrintWriter outWriter, + WrappedResponse response) { // Sets the response type response.setContentType("application/json; charset=UTF-8"); // some dirt to prevent cross site scripting @@ -2000,47 +1961,6 @@ public abstract class AbstractCommunicationManager implements } /** - * Gets the Paintable Id. If Paintable has debug id set it will be used - * prefixed with "PID_S". Otherwise a sequenced ID is created. - * - * @param paintable - * @return the paintable Id. - */ - public String getPaintableId(Paintable paintable) { - - String id = paintableIdMap.get(paintable); - if (id == null) { - // use testing identifier as id if set - String debugId = paintable.getDebugId(); - if (debugId == null) { - id = "PID" + idSequence++; - } else { - id = "PID_S" + debugId; - for (int seq = 0;; ++seq) { - // In case of a duplicate debug id, uniquify the PID by - // inserting a sequential integer. Try successive numbers - // until finding a PID that is either not used at all or - // used by a detached component. See #5109. - Paintable old = idPaintableMap.get(id); - if (old == null - || (old instanceof Component && ((Component) old) - .getApplication() == null)) { - break; - } - id = "PID_" + seq + "S" + debugId; - } - } - idPaintableMap.put(id, paintable); - paintableIdMap.put(paintable, id); - } - return id; - } - - public boolean hasPaintableId(Paintable paintable) { - return paintableIdMap.containsKey(paintable); - } - - /** * Returns dirty components which are in given window. Components in an * invisible subtrees are omitted. * @@ -2048,110 +1968,16 @@ public abstract class AbstractCommunicationManager implements * root window for which dirty components is to be fetched * @return */ - private ArrayList<Paintable> getDirtyVisibleComponents(Window w) { - final ArrayList<Paintable> resultset = new ArrayList<Paintable>( - dirtyPaintables); - - // The following algorithm removes any components that would be painted - // as a direct descendant of other components from the dirty components - // list. The result is that each component should be painted exactly - // once and any unmodified components will be painted as "cached=true". - - for (final Iterator<Paintable> i = dirtyPaintables.iterator(); i - .hasNext();) { - final Paintable p = i.next(); - if (p instanceof Component) { - final Component component = (Component) p; - if (component.getApplication() == null) { - // component is detached after requestRepaint is called - resultset.remove(p); - i.remove(); - } else { - Window componentsRoot = component.getWindow(); - if (componentsRoot == null) { - // This should not happen unless somebody has overriden - // getApplication or getWindow in an illegal way. - throw new IllegalStateException( - "component.getWindow() returned null for a component attached to the application"); - } - if (componentsRoot.getParent() != null) { - // this is a subwindow - componentsRoot = componentsRoot.getParent(); - } - if (componentsRoot != w) { - resultset.remove(p); - } else if (component.getParent() != null - && !component.getParent().isVisible()) { - /* - * Do not return components in an invisible subtree. - * - * Components that are invisible in visible subree, must - * be rendered (to let client know that they need to be - * hidden). - */ - resultset.remove(p); - } - } + private ArrayList<Component> getDirtyVisibleComponents( + DirtyConnectorTracker dirtyConnectorTracker) { + ArrayList<Component> dirtyComponents = new ArrayList<Component>(); + for (Component c : dirtyConnectorTracker.getDirtyComponents()) { + if (isVisible(c)) { + dirtyComponents.add(c); } } - return resultset; - } - - /** - * @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent) - */ - public void repaintRequested(RepaintRequestEvent event) { - final Paintable p = event.getPaintable(); - if (!dirtyPaintables.contains(p)) { - dirtyPaintables.add(p); - } - } - - /** - * Internally mark a {@link Paintable} as painted and start collecting new - * repaint requests for it. - * - * @param paintable - */ - private void paintablePainted(Paintable paintable) { - dirtyPaintables.remove(paintable); - paintable.requestRepaintRequests(); - } - - /** - * Implementation of {@link URIHandler.ErrorEvent} interface. - */ - public class URIHandlerErrorImpl implements URIHandler.ErrorEvent, - Serializable { - - private final URIHandler owner; - - private final Throwable throwable; - - /** - * - * @param owner - * @param throwable - */ - private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) { - this.owner = owner; - this.throwable = throwable; - } - - /** - * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable() - */ - public Throwable getThrowable() { - return throwable; - } - - /** - * @see com.vaadin.terminal.URIHandler.ErrorEvent#getURIHandler() - */ - public URIHandler getURIHandler() { - return owner; - } + return dirtyComponents; } /** @@ -2196,23 +2022,6 @@ public abstract class AbstractCommunicationManager implements } } - /** - * Helper method to test if a component contains another - * - * @param parent - * @param child - */ - private static boolean isChildOf(Component parent, Component child) { - Component p = child.getParent(); - while (p != null) { - if (parent == p) { - return true; - } - p = p.getParent(); - } - return false; - } - protected class InvalidUIDLSecurityKeyException extends GeneralSecurityException { @@ -2222,87 +2031,19 @@ public abstract class AbstractCommunicationManager implements } - /** - * Calls the Window URI handler for a request and returns the - * {@link DownloadStream} returned by the handler. - * - * If the window is the main window of an application, the (deprecated) - * {@link Application#handleURI(java.net.URL, String)} is called first to - * handle {@link ApplicationResource}s, and the window handler is only - * called if it returns null. - * - * @param window - * the target window of the request - * @param request - * the request instance - * @param response - * the response to write to - * @return DownloadStream if the request was handled and further processing - * should be suppressed, null otherwise. - * @see com.vaadin.terminal.URIHandler - */ - @SuppressWarnings("deprecation") - protected DownloadStream handleURI(Window window, Request request, - Response response, Callback callback) { - - String uri = callback.getRequestPathInfo(request); - - // If no URI is available - if (uri == null) { - uri = ""; - } else { - // Removes the leading / - while (uri.startsWith("/") && uri.length() > 0) { - uri = uri.substring(1); - } - } - - // Handles the uri - try { - URL context = application.getURL(); - if (window == application.getMainWindow()) { - DownloadStream stream = null; - /* - * Application.handleURI run first. Handles possible - * ApplicationResources. - */ - stream = application.handleURI(context, uri); - if (stream == null) { - stream = window.handleURI(context, uri); - } - return stream; - } else { - // Resolve the prefix end index - final int index = uri.indexOf('/'); - if (index > 0) { - String prefix = uri.substring(0, index); - URL windowContext; - windowContext = new URL(context, prefix + "/"); - final String windowUri = (uri.length() > prefix.length() + 1) ? uri - .substring(prefix.length() + 1) : ""; - return window.handleURI(windowContext, windowUri); - } else { - return null; - } - } - - } catch (final Throwable t) { - application.getErrorHandler().terminalError( - new URIHandlerErrorImpl(application, t)); - return null; - } - } - - private final HashMap<Class<? extends Paintable>, Integer> typeToKey = new HashMap<Class<? extends Paintable>, Integer>(); + private final HashMap<Class<? extends ClientConnector>, Integer> typeToKey = new HashMap<Class<? extends ClientConnector>, Integer>(); private int nextTypeKey = 0; - String getTagForType(Class<? extends Paintable> class1) { - Integer object = typeToKey.get(class1); - if (object == null) { - object = nextTypeKey++; - typeToKey.put(class1, object); + private BootstrapHandler bootstrapHandler; + + String getTagForType(Class<? extends ClientConnector> class1) { + Integer id = typeToKey.get(class1); + if (id == null) { + id = nextTypeKey++; + typeToKey.put(class1, id); + logger.log(Level.FINE, "Mapping " + class1.getName() + " to " + id); } - return object.toString(); + return id.toString(); } /** @@ -2311,7 +2052,7 @@ public abstract class AbstractCommunicationManager implements * * TODO make customlayout templates (from theme) to be cached here. */ - class OpenWindowCache implements Serializable { + class ClientCache implements Serializable { private final Set<Object> res = new HashSet<Object>(); @@ -2330,10 +2071,116 @@ public abstract class AbstractCommunicationManager implements } - abstract String getStreamVariableTargetUrl(VariableOwner owner, - String name, StreamVariable value); + abstract String getStreamVariableTargetUrl(Connector owner, String name, + StreamVariable value); - abstract protected void cleanStreamVariable(VariableOwner owner, String name); + abstract protected void cleanStreamVariable(Connector owner, String name); + + /** + * Gets the bootstrap handler that should be used for generating the pages + * bootstrapping applications for this communication manager. + * + * @return the bootstrap handler to use + */ + private BootstrapHandler getBootstrapHandler() { + if (bootstrapHandler == null) { + bootstrapHandler = createBootstrapHandler(); + } + + return bootstrapHandler; + } + + protected abstract BootstrapHandler createBootstrapHandler(); + + protected boolean handleApplicationRequest(WrappedRequest request, + WrappedResponse response) throws IOException { + return application.handleRequest(request, response); + } + + public void handleBrowserDetailsRequest(WrappedRequest request, + WrappedResponse response, Application application) + throws IOException { + + // if we do not yet have a currentRoot, it should be initialized + // shortly, and we should send the initial UIDL + boolean sendUIDL = Root.getCurrentRoot() == null; + + try { + CombinedRequest combinedRequest = new CombinedRequest(request); + + Root root = application.getRootForRequest(combinedRequest); + response.setContentType("application/json; charset=UTF-8"); + + // Use the same logic as for determined roots + BootstrapHandler bootstrapHandler = getBootstrapHandler(); + BootstrapContext context = bootstrapHandler.createContext( + combinedRequest, response, application, root.getRootId()); + + String widgetset = context.getWidgetsetName(); + String theme = context.getThemeName(); + String themeUri = bootstrapHandler.getThemeUri(context, theme); + + // TODO These are not required if it was only the init of the root + // that was delayed + JSONObject params = new JSONObject(); + params.put("widgetset", widgetset); + params.put("themeUri", themeUri); + // Root id might have changed based on e.g. window.name + params.put(ApplicationConnection.ROOT_ID_PARAMETER, + root.getRootId()); + if (sendUIDL) { + String initialUIDL = getInitialUIDL(combinedRequest, root); + params.put("uidl", initialUIDL); + } + + // NOTE! GateIn requires, for some weird reason, getOutputStream + // to be used instead of getWriter() (it seems to interpret + // application/json as a binary content type) + final OutputStream out = response.getOutputStream(); + final PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + + outWriter.write(params.toString()); + // NOTE GateIn requires the buffers to be flushed to work + outWriter.flush(); + out.flush(); + } catch (RootRequiresMoreInformationException e) { + // Requiring more information at this point is not allowed + // TODO handle in a better way + throw new RuntimeException(e); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Generates the initial UIDL message that can e.g. be included in a html + * page to avoid a separate round trip just for getting the UIDL. + * + * @param request + * the request that caused the initialization + * @param root + * the root for which the UIDL should be generated + * @return a string with the initial UIDL message + * @throws PaintException + * if an exception occurs while painting + */ + protected String getInitialUIDL(WrappedRequest request, Root root) + throws PaintException { + // TODO maybe unify writeUidlResponse()? + StringWriter sWriter = new StringWriter(); + PrintWriter pWriter = new PrintWriter(sWriter); + pWriter.print("{"); + if (isXSRFEnabled(root.getApplication())) { + pWriter.print(getSecurityKeyUIDL(request)); + } + writeUidlResponse(request, true, pWriter, root, false); + pWriter.print("}"); + String initialUIDL = sWriter.toString(); + logger.log(Level.FINE, "Initial UIDL:" + initialUIDL); + return initialUIDL; + } /** * Stream that extracts content from another stream until the boundary @@ -2484,4 +2331,5 @@ public abstract class AbstractCommunicationManager implements return b; } } + } diff --git a/src/com/vaadin/terminal/gwt/server/ApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/ApplicationPortlet.java deleted file mode 100644 index f971bcbc90..0000000000 --- a/src/com/vaadin/terminal/gwt/server/ApplicationPortlet.java +++ /dev/null @@ -1,250 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.server; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.Serializable; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.portlet.ActionRequest; -import javax.portlet.ActionResponse; -import javax.portlet.PortalContext; -import javax.portlet.Portlet; -import javax.portlet.PortletConfig; -import javax.portlet.PortletException; -import javax.portlet.PortletRequestDispatcher; -import javax.portlet.PortletSession; -import javax.portlet.RenderRequest; -import javax.portlet.RenderResponse; - -import com.liferay.portal.kernel.util.PropsUtil; -import com.vaadin.Application; - -/** - * Portlet main class for Portlet 1.0 (JSR-168) portlets which consist of a - * portlet and a servlet. For Portlet 2.0 (JSR-286, no servlet required), use - * {@link ApplicationPortlet2} instead. - */ -@SuppressWarnings("serial") -public class ApplicationPortlet implements Portlet, Serializable { - // portlet configuration parameters - private static final String PORTLET_PARAMETER_APPLICATION = "application"; - private static final String PORTLET_PARAMETER_STYLE = "style"; - private static final String PORTLET_PARAMETER_WIDGETSET = "widgetset"; - - // The application to show - protected String app = null; - // some applications might require forced height (and, more seldom, width) - protected String style = null; // e.g "height:500px;" - // force the portlet to use this widgetset - portlet level setting - protected String portletWidgetset = null; - - public void destroy() { - - } - - public void init(PortletConfig config) throws PortletException { - app = config.getInitParameter(PORTLET_PARAMETER_APPLICATION); - if (app == null) { - throw new PortletException( - "No porlet application url defined in portlet.xml. Define the '" - + PORTLET_PARAMETER_APPLICATION - + "' init parameter to be the servlet deployment path."); - } - style = config.getInitParameter(PORTLET_PARAMETER_STYLE); - // enable forcing the selection of the widgetset in portlet - // configuration for a single portlet (backwards compatibility) - portletWidgetset = config.getInitParameter(PORTLET_PARAMETER_WIDGETSET); - } - - public void processAction(ActionRequest request, ActionResponse response) - throws PortletException, IOException { - PortletApplicationContext.dispatchRequest(this, request, response); - } - - public void render(RenderRequest request, RenderResponse response) - throws PortletException, IOException { - - // display the Vaadin application - writeAjaxWindow(request, response); - } - - protected void writeAjaxWindow(RenderRequest request, - RenderResponse response) throws IOException { - - response.setContentType("text/html"); - if (app != null) { - PortletSession sess = request.getPortletSession(); - PortletApplicationContext ctx = PortletApplicationContext - .getApplicationContext(sess); - - PortletRequestDispatcher dispatcher = sess.getPortletContext() - .getRequestDispatcher("/" + app); - - try { - // portal-wide settings - PortalContext portalCtx = request.getPortalContext(); - - boolean isLifeRay = portalCtx.getPortalInfo().toLowerCase() - .contains("liferay"); - - request.setAttribute(ApplicationServlet.REQUEST_FRAGMENT, - "true"); - - // fixed base theme to use - all portal pages with Vaadin - // applications will load this exactly once - String portalTheme = getPortalProperty( - Constants.PORTAL_PARAMETER_VAADIN_THEME, portalCtx); - - String portalWidgetset = getPortalProperty( - Constants.PORTAL_PARAMETER_VAADIN_WIDGETSET, portalCtx); - - // location of the widgetset(s) and default theme (to which - // /VAADIN/widgetsets/... - // is appended) - String portalResourcePath = getPortalProperty( - Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH, - portalCtx); - - if (portalResourcePath != null) { - // if portalResourcePath is defined, set it as a request - // parameter which will override the default location in - // servlet - request.setAttribute( - ApplicationServlet.REQUEST_VAADIN_STATIC_FILE_PATH, - portalResourcePath); - } - - // - if the user has specified a widgetset for this portlet, use - // it from the portlet (not fully supported) - // - otherwise, if specified, use the portal-wide widgetset - // and widgetset path settings (recommended) - // - finally, default to use the default widgetset if nothing - // else is found - if (portletWidgetset != null) { - request.setAttribute(ApplicationServlet.REQUEST_WIDGETSET, - portletWidgetset); - } - if (portalWidgetset != null) { - request.setAttribute( - ApplicationServlet.REQUEST_SHARED_WIDGETSET, - portalWidgetset); - } - - if (style != null) { - request.setAttribute(ApplicationServlet.REQUEST_APPSTYLE, - style); - } - - // portalTheme is only used if the shared portal resource - // directory is defined - if (portalTheme != null && portalResourcePath != null) { - request.setAttribute( - ApplicationServlet.REQUEST_DEFAULT_THEME, - portalTheme); - - String defaultThemeUri = null; - defaultThemeUri = portalResourcePath + "/" - + AbstractApplicationServlet.THEME_DIRECTORY_PATH - + portalTheme; - /* - * Make sure portal default Vaadin theme is included in DOM. - * Vaadin portlet themes do not "inherit" base theme, so we - * need to force loading of the common base theme. - */ - OutputStream out = response.getPortletOutputStream(); - - // Using portal-wide theme - String loadDefaultTheme = ("<script type=\"text/javascript\">\n" - + "if(!vaadin) { var vaadin = {} } \n" - + "if(!vaadin.themesLoaded) { vaadin.themesLoaded = {} } \n" - + "if(!vaadin.themesLoaded['" - + portalTheme - + "']) {\n" - + "var stylesheet = document.createElement('link');\n" - + "stylesheet.setAttribute('rel', 'stylesheet');\n" - + "stylesheet.setAttribute('type', 'text/css');\n" - + "stylesheet.setAttribute('href', '" - + defaultThemeUri - + "/styles.css');\n" - + "document.getElementsByTagName('head')[0].appendChild(stylesheet);\n" - + "vaadin.themesLoaded['" - + portalTheme - + "'] = true;\n}\n" + "</script>\n"); - out.write(loadDefaultTheme.getBytes()); - } - - dispatcher.include(request, response); - - if (isLifeRay) { - /* - * Temporary support to heartbeat Liferay session when using - * Vaadin based portlet. We hit an extra xhr to liferay - * servlet to extend the session lifetime after each Vaadin - * request. This hack can be removed when supporting portlet - * 2.0 and resourceRequests. - * - * TODO make this configurable, this is not necessary with - * some custom session configurations. - */ - OutputStream out = response.getPortletOutputStream(); - - String lifeRaySessionHearbeatHack = ("<script type=\"text/javascript\">" - + "if(!vaadin.postRequestHooks) {" - + " vaadin.postRequestHooks = {};" - + "}" - + "vaadin.postRequestHooks.liferaySessionHeartBeat = function() {" - + " if (Liferay && Liferay.Session && Liferay.Session.setCookie) {" - + " Liferay.Session.setCookie();" - + " }" - + "};" + "</script>"); - out.write(lifeRaySessionHearbeatHack.getBytes()); - } - - } catch (PortletException e) { - PrintWriter out = response.getWriter(); - out.print("<h1>Servlet include failed!</h1>"); - Logger.getLogger(AbstractApplicationPortlet.class.getName()) - .log(Level.WARNING, "Servlet include failed", e); - ctx.setPortletApplication(this, null); - return; - } - - Application app = (Application) request - .getAttribute(Application.class.getName()); - ctx.setPortletApplication(this, app); - ctx.firePortletRenderRequest(this, request, response); - - } - } - - private String getPortalProperty(String name, PortalContext context) { - boolean isLifeRay = context.getPortalInfo().toLowerCase() - .contains("liferay"); - - // TODO test on non-LifeRay platforms - - String value; - if (isLifeRay) { - value = getLifeRayPortalProperty(name); - } else { - value = context.getProperty(name); - } - - return value; - } - - private String getLifeRayPortalProperty(String name) { - String value; - try { - value = PropsUtil.get(name); - } catch (Exception e) { - value = null; - } - return value; - } -} diff --git a/src/com/vaadin/terminal/gwt/server/ApplicationPortlet2.java b/src/com/vaadin/terminal/gwt/server/ApplicationPortlet2.java index 3e15c1cf8f..7a46a07e6c 100644 --- a/src/com/vaadin/terminal/gwt/server/ApplicationPortlet2.java +++ b/src/com/vaadin/terminal/gwt/server/ApplicationPortlet2.java @@ -8,6 +8,7 @@ import javax.portlet.PortletConfig; import javax.portlet.PortletException; import com.vaadin.Application; +import com.vaadin.terminal.gwt.server.ServletPortletHelper.ApplicationClassException; /** * TODO Write documentation, fix JavaDoc tags. @@ -18,23 +19,16 @@ public class ApplicationPortlet2 extends AbstractApplicationPortlet { private Class<? extends Application> applicationClass; - @SuppressWarnings("unchecked") @Override public void init(PortletConfig config) throws PortletException { super.init(config); - final String applicationClassName = config - .getInitParameter("application"); - if (applicationClassName == null) { - throw new PortletException( - "Application not specified in portlet parameters"); - } - try { - applicationClass = (Class<? extends Application>) getClassLoader() - .loadClass(applicationClassName); - } catch (final ClassNotFoundException e) { - throw new PortletException("Failed to load application class: " - + applicationClassName); + applicationClass = ServletPortletHelper.getApplicationClass( + config.getInitParameter("application"), + config.getInitParameter(Application.ROOT_PARAMETER), + getClassLoader()); + } catch (ApplicationClassException e) { + throw new PortletException(e); } } diff --git a/src/com/vaadin/terminal/gwt/server/ApplicationResourceHandler.java b/src/com/vaadin/terminal/gwt/server/ApplicationResourceHandler.java new file mode 100644 index 0000000000..7cf66d5fcf --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ApplicationResourceHandler.java @@ -0,0 +1,54 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletResponse; + +import com.vaadin.Application; +import com.vaadin.terminal.ApplicationResource; +import com.vaadin.terminal.DownloadStream; +import com.vaadin.terminal.RequestHandler; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; + +public class ApplicationResourceHandler implements RequestHandler { + private static final Pattern APP_RESOURCE_PATTERN = Pattern + .compile("^/?APP/(\\d+)/.*"); + + public boolean handleRequest(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException { + // Check for application resources + String requestPath = request.getRequestPathInfo(); + if (requestPath == null) { + return false; + } + Matcher resourceMatcher = APP_RESOURCE_PATTERN.matcher(requestPath); + + if (resourceMatcher.matches()) { + ApplicationResource resource = application + .getResource(resourceMatcher.group(1)); + if (resource != null) { + DownloadStream stream = resource.getStream(); + if (stream != null) { + stream.setCacheTime(resource.getCacheTime()); + stream.writeTo(response); + return true; + } + } + // We get here if the url looks like an application resource but no + // resource can be served + response.sendError(HttpServletResponse.SC_NOT_FOUND, + request.getRequestPathInfo() + " can not be found"); + return true; + } + + return false; + } +} diff --git a/src/com/vaadin/terminal/gwt/server/ApplicationRunnerServlet.java b/src/com/vaadin/terminal/gwt/server/ApplicationRunnerServlet.java deleted file mode 100644 index a9eaa1bb82..0000000000 --- a/src/com/vaadin/terminal/gwt/server/ApplicationRunnerServlet.java +++ /dev/null @@ -1,218 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.server; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.vaadin.Application; - -@SuppressWarnings("serial") -public class ApplicationRunnerServlet extends AbstractApplicationServlet { - - private static final Logger logger = Logger - .getLogger(ApplicationRunnerServlet.class.getName()); - - /** - * The name of the application class currently used. Only valid within one - * request. - */ - private String[] defaultPackages; - private final ThreadLocal<HttpServletRequest> request = new ThreadLocal<HttpServletRequest>(); - - @Override - public void init(ServletConfig servletConfig) throws ServletException { - super.init(servletConfig); - String initParameter = servletConfig - .getInitParameter("defaultPackages"); - if (initParameter != null) { - defaultPackages = initParameter.split(","); - } - } - - @Override - protected void service(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - this.request.set(request); - super.service(request, response); - this.request.set(null); - } - - @Override - protected URL getApplicationUrl(HttpServletRequest request) - throws MalformedURLException { - URL url = super.getApplicationUrl(request); - - String path = url.toString(); - path += getApplicationRunnerApplicationClassName(request); - path += "/"; - - return new URL(path); - } - - @Override - protected Application getNewApplication(HttpServletRequest request) - throws ServletException { - - // Creates a new application instance - try { - final Application application = getApplicationClass().newInstance(); - return application; - } catch (final IllegalAccessException e) { - throw new ServletException(e); - } catch (final InstantiationException e) { - throw new ServletException(e); - } catch (final ClassNotFoundException e) { - throw new ServletException( - new InstantiationException( - "Failed to load application class: " - + getApplicationRunnerApplicationClassName(request))); - } - - } - - private String getApplicationRunnerApplicationClassName( - HttpServletRequest request) { - return getApplicationRunnerURIs(request).applicationClassname; - } - - private static class URIS { - String staticFilesPath; - // String applicationURI; - // String context; - // String runner; - String applicationClassname; - - } - - /** - * Parses application runner URIs. - * - * If request URL is e.g. - * http://localhost:8080/vaadin/run/com.vaadin.demo.Calc then - * <ul> - * <li>context=vaadin</li> - * <li>Runner servlet=run</li> - * <li>Vaadin application=com.vaadin.demo.Calc</li> - * </ul> - * - * @param request - * @return string array containing widgetset URI, application URI and - * context, runner, application classname - */ - private static URIS getApplicationRunnerURIs(HttpServletRequest request) { - final String[] urlParts = request.getRequestURI().toString() - .split("\\/"); - String context = null; - // String runner = null; - URIS uris = new URIS(); - String applicationClassname = null; - String contextPath = request.getContextPath(); - if (urlParts[1].equals(contextPath.replaceAll("\\/", ""))) { - // class name comes after web context and runner application - context = urlParts[1]; - // runner = urlParts[2]; - if (urlParts.length == 3) { - throw new IllegalArgumentException("No application specified"); - } - applicationClassname = urlParts[3]; - - uris.staticFilesPath = "/" + context; - // uris.applicationURI = "/" + context + "/" + runner + "/" - // + applicationClassname; - // uris.context = context; - // uris.runner = runner; - uris.applicationClassname = applicationClassname; - } else { - // no context - context = ""; - // runner = urlParts[1]; - if (urlParts.length == 2) { - throw new IllegalArgumentException("No application specified"); - } - applicationClassname = urlParts[2]; - - uris.staticFilesPath = "/"; - // uris.applicationURI = "/" + runner + "/" + applicationClassname; - // uris.context = context; - // uris.runner = runner; - uris.applicationClassname = applicationClassname; - } - return uris; - } - - @SuppressWarnings("unchecked") - @Override - protected Class<? extends Application> getApplicationClass() - throws ClassNotFoundException { - // TODO use getClassLoader() ? - - Class<? extends Application> appClass = null; - - String baseName = getApplicationRunnerApplicationClassName(request - .get()); - try { - appClass = (Class<? extends Application>) getClass() - .getClassLoader().loadClass(baseName); - return appClass; - } catch (Exception e) { - // - if (defaultPackages != null) { - for (int i = 0; i < defaultPackages.length; i++) { - try { - appClass = (Class<? extends Application>) getClass() - .getClassLoader().loadClass( - defaultPackages[i] + "." + baseName); - } catch (ClassNotFoundException ee) { - // Ignore as this is expected for many packages - } catch (Exception e2) { - // TODO: handle exception - logger.log( - Level.FINE, - "Failed to find application class in the default package.", - e2); - } - if (appClass != null) { - return appClass; - } - } - } - - } - - throw new ClassNotFoundException(); - } - - @Override - protected String getRequestPathInfo(HttpServletRequest request) { - String path = request.getPathInfo(); - if (path == null) { - return null; - } - - path = path.substring(1 + getApplicationRunnerApplicationClassName( - request).length()); - return path; - } - - @Override - protected String getStaticFilesLocation(HttpServletRequest request) { - URIS uris = getApplicationRunnerURIs(request); - String staticFilesPath = uris.staticFilesPath; - if (staticFilesPath.equals("/")) { - staticFilesPath = ""; - } - - return staticFilesPath; - } - -} diff --git a/src/com/vaadin/terminal/gwt/server/ApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/ApplicationServlet.java index 8ad763f5d1..2c4d38ef24 100644 --- a/src/com/vaadin/terminal/gwt/server/ApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/ApplicationServlet.java @@ -8,6 +8,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import com.vaadin.Application; +import com.vaadin.terminal.gwt.server.ServletPortletHelper.ApplicationClassException; /** * This servlet connects a Vaadin Application to Web. @@ -35,7 +36,6 @@ public class ApplicationServlet extends AbstractApplicationServlet { * if an exception has occurred that interferes with the * servlet's normal operation. */ - @SuppressWarnings("unchecked") @Override public void init(javax.servlet.ServletConfig servletConfig) throws javax.servlet.ServletException { @@ -44,20 +44,13 @@ public class ApplicationServlet extends AbstractApplicationServlet { // Loads the application class using the same class loader // as the servlet itself - // Gets the application class name - final String applicationClassName = servletConfig - .getInitParameter("application"); - if (applicationClassName == null) { - throw new ServletException( - "Application not specified in servlet parameters"); - } - try { - applicationClass = (Class<? extends Application>) getClassLoader() - .loadClass(applicationClassName); - } catch (final ClassNotFoundException e) { - throw new ServletException("Failed to load application class: " - + applicationClassName); + applicationClass = ServletPortletHelper.getApplicationClass( + servletConfig.getInitParameter("application"), + servletConfig.getInitParameter(Application.ROOT_PARAMETER), + getClassLoader()); + } catch (ApplicationClassException e) { + throw new ServletException(e); } } @@ -84,4 +77,4 @@ public class ApplicationServlet extends AbstractApplicationServlet { throws ClassNotFoundException { return applicationClass; } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java new file mode 100644 index 0000000000..84f87124d3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java @@ -0,0 +1,602 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.io.Writer; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import com.vaadin.Application; +import com.vaadin.RootRequiresMoreInformationException; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.PaintException; +import com.vaadin.terminal.RequestHandler; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.ui.Root; + +public abstract class BootstrapHandler implements RequestHandler { + + protected class BootstrapContext implements Serializable { + + private final WrappedResponse response; + private final WrappedRequest request; + private final Application application; + private final Integer rootId; + + private Writer writer; + private Root root; + private String widgetsetName; + private String themeName; + private String appId; + + private boolean rootFetched = false; + + public BootstrapContext(WrappedResponse response, + WrappedRequest request, Application application, Integer rootId) { + this.response = response; + this.request = request; + this.application = application; + this.rootId = rootId; + } + + public WrappedResponse getResponse() { + return response; + } + + public WrappedRequest getRequest() { + return request; + } + + public Application getApplication() { + return application; + } + + public Writer getWriter() throws IOException { + if (writer == null) { + response.setContentType("text/html"); + writer = new BufferedWriter(new OutputStreamWriter( + response.getOutputStream(), "UTF-8")); + } + return writer; + } + + public Integer getRootId() { + return rootId; + } + + public Root getRoot() { + if (!rootFetched) { + root = Root.getCurrentRoot(); + rootFetched = true; + } + return root; + } + + public String getWidgetsetName() { + if (widgetsetName == null) { + Root root = getRoot(); + if (root != null) { + widgetsetName = getWidgetsetForRoot(this); + } + } + return widgetsetName; + } + + public String getThemeName() { + if (themeName == null) { + Root root = getRoot(); + if (root != null) { + themeName = findAndEscapeThemeName(this); + } + } + return themeName; + } + + public String getAppId() { + if (appId == null) { + appId = getApplicationId(this); + } + return appId; + } + + } + + public boolean handleRequest(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException { + + // TODO Should all urls be handled here? + Integer rootId = null; + try { + Root root = application.getRootForRequest(request); + if (root == null) { + writeError(response, new Throwable("No Root found")); + return true; + } + + rootId = Integer.valueOf(root.getRootId()); + } catch (RootRequiresMoreInformationException e) { + // Just keep going without rootId + } + + try { + writeBootstrapPage(request, response, application, rootId); + } catch (JSONException e) { + writeError(response, e); + } + + return true; + } + + protected final void writeBootstrapPage(WrappedRequest request, + WrappedResponse response, Application application, Integer rootId) + throws IOException, JSONException { + + BootstrapContext context = createContext(request, response, + application, rootId); + + DeploymentConfiguration deploymentConfiguration = request + .getDeploymentConfiguration(); + + boolean standalone = deploymentConfiguration.isStandalone(request); + if (standalone) { + setBootstrapPageHeaders(context); + writeBootstrapPageHtmlHeadStart(context); + writeBootstrapPageHtmlHeader(context); + writeBootstrapPageHtmlBodyStart(context); + } + + // TODO include initial UIDL in the scripts? + writeBootstrapPageHtmlVaadinScripts(context); + + writeBootstrapPageHtmlMainDiv(context); + + Writer page = context.getWriter(); + if (standalone) { + page.write("</body>\n</html>\n"); + } + + page.close(); + } + + public BootstrapContext createContext(WrappedRequest request, + WrappedResponse response, Application application, Integer rootId) { + BootstrapContext context = new BootstrapContext(response, request, + application, rootId); + return context; + } + + protected String getMainDivStyle(BootstrapContext context) { + return null; + } + + /** + * Creates and returns a unique ID for the DIV where the application is to + * be rendered. + * + * @param context + * + * @return the id to use in the DOM + */ + protected abstract String getApplicationId(BootstrapContext context); + + public String getWidgetsetForRoot(BootstrapContext context) { + Root root = context.getRoot(); + WrappedRequest request = context.getRequest(); + + String widgetset = root.getApplication().getWidgetsetForRoot(root); + if (widgetset == null) { + widgetset = request.getDeploymentConfiguration() + .getConfiguredWidgetset(request); + } + + widgetset = AbstractApplicationServlet.stripSpecialChars(widgetset); + return widgetset; + } + + /** + * Method to write the div element into which that actual Vaadin application + * is rendered. + * <p> + * Override this method if you want to add some custom html around around + * the div element into which the actual Vaadin application will be + * rendered. + * + * @param context + * + * @throws IOException + */ + protected void writeBootstrapPageHtmlMainDiv(BootstrapContext context) + throws IOException { + Writer page = context.getWriter(); + String style = getMainDivStyle(context); + + /*- Add classnames; + * .v-app + * .v-app-loading + * .v-app-<simpleName for app class> + *- Additionally added from javascript: + * .v-theme-<themeName, remove non-alphanum> + */ + + String appClass = "v-app-" + + getApplicationCSSClassName(context.getApplication()); + + String classNames = "v-app " + appClass; + + if (style != null && style.length() != 0) { + style = " style=\"" + style + "\""; + } + page.write("<div id=\"" + context.getAppId() + "\" class=\"" + + classNames + "\"" + style + ">"); + page.write("<div class=\"v-app-loading\"></div>"); + page.write("</div>\n"); + page.write("<noscript>" + getNoScriptMessage() + "</noscript>"); + } + + /** + * Returns a message printed for browsers without scripting support or if + * browsers scripting support is disabled. + */ + protected String getNoScriptMessage() { + return "You have to enable javascript in your browser to use an application built with Vaadin."; + } + + /** + * Returns the application class identifier for use in the application CSS + * class name in the root DIV. The application CSS class name is of form + * "v-app-"+getApplicationCSSClassName(). + * + * This method should normally not be overridden. + * + * @return The CSS class name to use in combination with "v-app-". + */ + protected String getApplicationCSSClassName(Application application) { + return application.getClass().getSimpleName(); + } + + /** + * + * Method to open the body tag of the html kickstart page. + * <p> + * This method is responsible for closing the head tag and opening the body + * tag. + * <p> + * Override this method if you want to add some custom html to the page. + * + * @throws IOException + */ + protected void writeBootstrapPageHtmlBodyStart(BootstrapContext context) + throws IOException { + Writer page = context.getWriter(); + page.write("\n</head>\n<body scroll=\"auto\" class=\"" + + ApplicationConnection.GENERATED_BODY_CLASSNAME + "\">\n"); + } + + /** + * Method to write the script part of the page which loads needed Vaadin + * scripts and themes. + * <p> + * Override this method if you want to add some custom html around scripts. + * + * @param context + * + * @throws IOException + * @throws JSONException + */ + protected void writeBootstrapPageHtmlVaadinScripts(BootstrapContext context) + throws IOException, JSONException { + WrappedRequest request = context.getRequest(); + Writer page = context.getWriter(); + + DeploymentConfiguration deploymentConfiguration = request + .getDeploymentConfiguration(); + String staticFileLocation = deploymentConfiguration + .getStaticFileLocation(request); + + page.write("<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" " + + "style=\"position:absolute;width:0;height:0;border:0;overflow:" + + "hidden;\" src=\"javascript:false\"></iframe>"); + + String bootstrapLocation = staticFileLocation + + "/VAADIN/vaadinBootstrap.js"; + page.write("<script type=\"text/javascript\" src=\""); + page.write(bootstrapLocation); + page.write("\"></script>\n"); + + page.write("<script type=\"text/javascript\">\n"); + page.write("//<![CDATA[\n"); + page.write("if (!window.vaadin) alert(" + + JSONObject.quote("Failed to load the bootstrap javascript: " + + bootstrapLocation) + ");\n"); + + writeMainScriptTagContents(context); + page.write("//]]>\n</script>\n"); + } + + protected void writeMainScriptTagContents(BootstrapContext context) + throws JSONException, IOException { + JSONObject defaults = getDefaultParameters(context); + JSONObject appConfig = getApplicationParameters(context); + + boolean isDebug = !context.getApplication().isProductionMode(); + Writer page = context.getWriter(); + + page.write("vaadin.setDefaults("); + printJsonObject(page, defaults, isDebug); + page.write(");\n"); + + page.write("vaadin.initApplication(\""); + page.write(context.getAppId()); + page.write("\","); + printJsonObject(page, appConfig, isDebug); + page.write(");\n"); + } + + private static void printJsonObject(Writer page, JSONObject jsonObject, + boolean isDebug) throws IOException, JSONException { + if (isDebug) { + page.write(jsonObject.toString(4)); + } else { + page.write(jsonObject.toString()); + } + } + + protected JSONObject getApplicationParameters(BootstrapContext context) + throws JSONException, PaintException { + Application application = context.getApplication(); + Integer rootId = context.getRootId(); + + JSONObject appConfig = new JSONObject(); + + if (rootId != null) { + appConfig.put(ApplicationConnection.ROOT_ID_PARAMETER, rootId); + } + + if (context.getThemeName() != null) { + appConfig.put("themeUri", + getThemeUri(context, context.getThemeName())); + } + + JSONObject versionInfo = new JSONObject(); + versionInfo.put("vaadinVersion", AbstractApplicationServlet.VERSION); + versionInfo.put("applicationVersion", application.getVersion()); + appConfig.put("versionInfo", versionInfo); + + appConfig.put("widgetset", context.getWidgetsetName()); + + if (rootId == null || application.isRootInitPending(rootId.intValue())) { + appConfig.put("initialPath", context.getRequest() + .getRequestPathInfo()); + + Map<String, String[]> parameterMap = context.getRequest() + .getParameterMap(); + appConfig.put("initialParams", parameterMap); + } else { + // write the initial UIDL into the config + appConfig.put("uidl", + getInitialUIDL(context.getRequest(), context.getRoot())); + } + + return appConfig; + } + + protected JSONObject getDefaultParameters(BootstrapContext context) + throws JSONException { + JSONObject defaults = new JSONObject(); + + WrappedRequest request = context.getRequest(); + Application application = context.getApplication(); + + // Get system messages + Application.SystemMessages systemMessages = AbstractApplicationServlet + .getSystemMessages(application.getClass()); + if (systemMessages != null) { + // Write the CommunicationError -message to client + JSONObject comErrMsg = new JSONObject(); + comErrMsg.put("caption", + systemMessages.getCommunicationErrorCaption()); + comErrMsg.put("message", + systemMessages.getCommunicationErrorMessage()); + comErrMsg.put("url", systemMessages.getCommunicationErrorURL()); + + defaults.put("comErrMsg", comErrMsg); + + JSONObject authErrMsg = new JSONObject(); + authErrMsg.put("caption", + systemMessages.getAuthenticationErrorCaption()); + authErrMsg.put("message", + systemMessages.getAuthenticationErrorMessage()); + authErrMsg.put("url", systemMessages.getAuthenticationErrorURL()); + + defaults.put("authErrMsg", authErrMsg); + } + + DeploymentConfiguration deploymentConfiguration = request + .getDeploymentConfiguration(); + String staticFileLocation = deploymentConfiguration + .getStaticFileLocation(request); + String widgetsetBase = staticFileLocation + "/" + + AbstractApplicationServlet.WIDGETSET_DIRECTORY_PATH; + defaults.put("widgetsetBase", widgetsetBase); + + if (!application.isProductionMode()) { + defaults.put("debug", true); + } + + if (deploymentConfiguration.isStandalone(request)) { + defaults.put("standalone", true); + } + + defaults.put("appUri", getAppUri(context)); + + return defaults; + } + + protected abstract String getAppUri(BootstrapContext context); + + /** + * Method to write the contents of head element in html kickstart page. + * <p> + * Override this method if you want to add some custom html to the header of + * the page. + * + * @throws IOException + */ + protected void writeBootstrapPageHtmlHeader(BootstrapContext context) + throws IOException { + Writer page = context.getWriter(); + String themeName = context.getThemeName(); + + page.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n"); + + // Chrome frame in all versions of IE (only if Chrome frame is + // installed) + page.write("<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\"/>\n"); + + page.write("<style type=\"text/css\">" + + "html, body {height:100%;margin:0;}</style>"); + + // Add favicon links + if (themeName != null) { + String themeUri = getThemeUri(context, themeName); + page.write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\"" + + themeUri + "/favicon.ico\" />"); + page.write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\"" + + themeUri + "/favicon.ico\" />"); + } + + Root root = context.getRoot(); + String title = ((root == null || root.getCaption() == null) ? "Vaadin " + + AbstractApplicationServlet.VERSION_MAJOR : root.getCaption()); + + page.write("<title>" + + AbstractApplicationServlet.safeEscapeForHtml(title) + + "</title>"); + } + + /** + * Method to set http request headers for the Vaadin kickstart page. + * <p> + * Override this method if you need to customize http headers of the page. + * + * @param context + */ + protected void setBootstrapPageHeaders(BootstrapContext context) { + WrappedResponse response = context.getResponse(); + + // Window renders are not cacheable + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setContentType("text/html; charset=UTF-8"); + } + + /** + * Method to write the beginning of the html page. + * <p> + * This method is responsible for writing appropriate doc type declarations + * and to open html and head tags. + * <p> + * Override this method if you want to add some custom html to the very + * beginning of the page. + * + * @param context + * @throws IOException + */ + protected void writeBootstrapPageHtmlHeadStart(BootstrapContext context) + throws IOException { + Writer page = context.getWriter(); + + // write html header + page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD " + + "XHTML 1.0 Transitional//EN\" " + + "\"http://www.w3.org/TR/xhtml1/" + + "DTD/xhtml1-transitional.dtd\">\n"); + + page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\"" + + ">\n<head>\n"); + } + + /** + * Get the URI for the application theme. + * + * A portal-wide default theme is fetched from the portal shared resource + * directory (if any), other themes from the portlet. + * + * @param context + * @param themeName + * + * @return + */ + public String getThemeUri(BootstrapContext context, String themeName) { + WrappedRequest request = context.getRequest(); + final String staticFilePath = request.getDeploymentConfiguration() + .getStaticFileLocation(request); + return staticFilePath + "/" + + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName; + } + + /** + * Override if required + * + * @param context + * @return + */ + public String getThemeName(BootstrapContext context) { + return context.getApplication().getThemeForRoot(context.getRoot()); + } + + /** + * Don not override. + * + * @param context + * @return + */ + public String findAndEscapeThemeName(BootstrapContext context) { + String themeName = getThemeName(context); + if (themeName == null) { + WrappedRequest request = context.getRequest(); + themeName = request.getDeploymentConfiguration() + .getConfiguredTheme(request); + } + + // XSS preventation, theme names shouldn't contain special chars anyway. + // The servlet denies them via url parameter. + themeName = AbstractApplicationServlet.stripSpecialChars(themeName); + + return themeName; + } + + protected void writeError(WrappedResponse response, Throwable e) + throws IOException { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + e.getLocalizedMessage()); + } + + /** + * Gets the initial UIDL message to send to the client. + * + * @param request + * the originating request + * @param root + * the root for which the UIDL should be generated + * @return a string with the initial UIDL message + * @throws PaintException + * if an exception occurs while painting the components + */ + protected abstract String getInitialUIDL(WrappedRequest request, Root root) + throws PaintException; + +} diff --git a/src/com/vaadin/terminal/gwt/server/ClientConnector.java b/src/com/vaadin/terminal/gwt/server/ClientConnector.java new file mode 100644 index 0000000000..7a1f0fad68 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.server; + +import java.util.List; + +import com.vaadin.terminal.gwt.client.Connector; + +/** + * Interface implemented by all connectors that are capable of communicating + * with the client side + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public interface ClientConnector extends Connector, RpcTarget { + /** + * Returns the list of pending server to client RPC calls and clears the + * list. + * + * @return an unmodifiable ordered list of pending server to client method + * calls (not null) + * + * @since 7.0 + */ + public List<ClientMethodInvocation> retrievePendingRpcCalls(); + + /** + * Checks if the communicator is enabled. An enabled communicator is allowed + * to receive messages from its counter-part. + * + * @return true if the connector can receive messages, false otherwise + */ + public boolean isConnectorEnabled(); +} diff --git a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java new file mode 100644 index 0000000000..99633a13d6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java @@ -0,0 +1,69 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.Serializable; +import java.lang.reflect.Method; + +/** + * Internal class for keeping track of pending server to client method + * invocations for a Connector. + * + * @since 7.0 + */ +public class ClientMethodInvocation implements Serializable, + Comparable<ClientMethodInvocation> { + private final ClientConnector connector; + private final String interfaceName; + private final String methodName; + private final Object[] parameters; + private Class<?>[] parameterTypes; + + // used for sorting calls between different connectors in the same Root + private final long sequenceNumber; + // TODO may cause problems when clustering etc. + private static long counter = 0; + + public ClientMethodInvocation(ClientConnector connector, + String interfaceName, Method method, Object[] parameters) { + this.connector = connector; + this.interfaceName = interfaceName; + methodName = method.getName(); + parameterTypes = method.getParameterTypes(); + this.parameters = (null != parameters) ? parameters : new Object[0]; + sequenceNumber = ++counter; + } + + public Class<?>[] getParameterTypes() { + return parameterTypes; + } + + public ClientConnector getConnector() { + return connector; + } + + public String getInterfaceName() { + return interfaceName; + } + + public String getMethodName() { + return methodName; + } + + public Object[] getParameters() { + return parameters; + } + + protected long getSequenceNumber() { + return sequenceNumber; + } + + public int compareTo(ClientMethodInvocation o) { + if (null == o) { + return 0; + } + return Long.signum(getSequenceNumber() - o.getSequenceNumber()); + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java index 9c67a03bb3..3dd2eb97fd 100644 --- a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java @@ -6,24 +6,21 @@ package com.vaadin.terminal.gwt.server; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; +import java.net.URL; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.UUID; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import javax.servlet.ServletContext; import com.vaadin.Application; -import com.vaadin.terminal.ApplicationResource; -import com.vaadin.terminal.DownloadStream; -import com.vaadin.terminal.Paintable; +import com.vaadin.terminal.PaintException; import com.vaadin.terminal.StreamVariable; -import com.vaadin.terminal.VariableOwner; -import com.vaadin.ui.Component; -import com.vaadin.ui.Window; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.ui.Root; /** * Application manager processes changes and paints for single application @@ -42,150 +39,6 @@ import com.vaadin.ui.Window; public class CommunicationManager extends AbstractCommunicationManager { /** - * Concrete wrapper class for {@link HttpServletRequest}. - * - * @see Request - */ - private static class HttpServletRequestWrapper implements Request { - - private final HttpServletRequest request; - - public HttpServletRequestWrapper(HttpServletRequest request) { - this.request = request; - } - - public Object getAttribute(String name) { - return request.getAttribute(name); - } - - public int getContentLength() { - return request.getContentLength(); - } - - public InputStream getInputStream() throws IOException { - return request.getInputStream(); - } - - public String getParameter(String name) { - return request.getParameter(name); - } - - public String getRequestID() { - return "RequestURL:" + request.getRequestURI(); - } - - public Session getSession() { - return new HttpSessionWrapper(request.getSession()); - } - - public Object getWrappedRequest() { - return request; - } - - public boolean isRunningInPortlet() { - return false; - } - - public void setAttribute(String name, Object o) { - request.setAttribute(name, o); - } - } - - /** - * Concrete wrapper class for {@link HttpServletResponse}. - * - * @see Response - */ - private static class HttpServletResponseWrapper implements Response { - - private final HttpServletResponse response; - - public HttpServletResponseWrapper(HttpServletResponse response) { - this.response = response; - } - - public OutputStream getOutputStream() throws IOException { - return response.getOutputStream(); - } - - public Object getWrappedResponse() { - return response; - } - - public void setContentType(String type) { - response.setContentType(type); - } - - } - - /** - * Concrete wrapper class for {@link HttpSession}. - * - * @see Session - */ - private static class HttpSessionWrapper implements Session { - - private final HttpSession session; - - public HttpSessionWrapper(HttpSession session) { - this.session = session; - } - - public Object getAttribute(String name) { - return session.getAttribute(name); - } - - public int getMaxInactiveInterval() { - return session.getMaxInactiveInterval(); - } - - public Object getWrappedSession() { - return session; - } - - public boolean isNew() { - return session.isNew(); - } - - public void setAttribute(String name, Object o) { - session.setAttribute(name, o); - } - - } - - private static class AbstractApplicationServletWrapper implements Callback { - - private final AbstractApplicationServlet servlet; - - public AbstractApplicationServletWrapper( - AbstractApplicationServlet servlet) { - this.servlet = servlet; - } - - public void criticalNotification(Request request, Response response, - String cap, String msg, String details, String outOfSyncURL) - throws IOException { - servlet.criticalNotification( - (HttpServletRequest) request.getWrappedRequest(), - (HttpServletResponse) response.getWrappedResponse(), cap, - msg, details, outOfSyncURL); - } - - public String getRequestPathInfo(Request request) { - return servlet.getRequestPathInfo((HttpServletRequest) request - .getWrappedRequest()); - } - - public InputStream getThemeResourceAsStream(String themeName, - String resource) throws IOException { - return servlet.getServletContext().getResourceAsStream( - "/" + AbstractApplicationServlet.THEME_DIRECTORY_PATH - + themeName + "/" + resource); - } - - } - - /** * @deprecated use {@link #CommunicationManager(Application)} instead * @param application * @param applicationServlet @@ -208,6 +61,8 @@ public class CommunicationManager extends AbstractCommunicationManager { /** * Handles file upload request submitted via Upload component. * + * @param application + * * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) * * @param request @@ -215,15 +70,15 @@ public class CommunicationManager extends AbstractCommunicationManager { * @throws IOException * @throws InvalidUIDLSecurityKeyException */ - public void handleFileUpload(HttpServletRequest request, - HttpServletResponse response) throws IOException, - InvalidUIDLSecurityKeyException { + public void handleFileUpload(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException, InvalidUIDLSecurityKeyException { /* * URI pattern: APP/UPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl */ - String pathInfo = request.getPathInfo(); + String pathInfo = request.getRequestPathInfo(); // strip away part until the data we are interested starts int startOfData = pathInfo .indexOf(AbstractApplicationServlet.UPLOAD_URL_PREFIX) @@ -238,22 +93,18 @@ public class CommunicationManager extends AbstractCommunicationManager { String secKey = streamVariableToSeckey.get(streamVariable); if (secKey.equals(parts[2])) { - VariableOwner source = getVariableOwner(paintableId); + Connector source = getConnector(application, paintableId); String contentType = request.getContentType(); - if (request.getContentType().contains("boundary")) { + if (contentType.contains("boundary")) { // Multipart requests contain boundary string - doHandleSimpleMultipartFileUpload( - new HttpServletRequestWrapper(request), - new HttpServletResponseWrapper(response), + doHandleSimpleMultipartFileUpload(request, response, streamVariable, variableName, source, contentType.split("boundary=")[1]); } else { // if boundary string does not exist, the posted file is from // XHR2.post(File) - doHandleXhrFilePost(new HttpServletRequestWrapper(request), - new HttpServletResponseWrapper(response), - streamVariable, variableName, source, - request.getContentLength()); + doHandleXhrFilePost(request, response, streamVariable, + variableName, source, request.getContentLength()); } } else { throw new InvalidUIDLSecurityKeyException( @@ -262,95 +113,27 @@ public class CommunicationManager extends AbstractCommunicationManager { } - /** - * Handles UIDL request - * - * TODO document - * - * @param request - * @param response - * @param applicationServlet - * @param window - * target window of the UIDL request, can be null if window not - * found - * @throws IOException - * @throws ServletException - */ - public void handleUidlRequest(HttpServletRequest request, - HttpServletResponse response, - AbstractApplicationServlet applicationServlet, Window window) - throws IOException, ServletException, - InvalidUIDLSecurityKeyException { - doHandleUidlRequest(new HttpServletRequestWrapper(request), - new HttpServletResponseWrapper(response), - new AbstractApplicationServletWrapper(applicationServlet), - window); - } - - /** - * Gets the existing application or creates a new one. Get a window within - * an application based on the requested URI. - * - * @param request - * the HTTP Request. - * @param application - * the Application to query for window. - * @param assumedWindow - * if the window has been already resolved once, this parameter - * must contain the window. - * @return Window matching the given URI or null if not found. - * @throws ServletException - * if an exception has occurred that interferes with the - * servlet's normal operation. - */ - Window getApplicationWindow(HttpServletRequest request, - AbstractApplicationServlet applicationServlet, - Application application, Window assumedWindow) - throws ServletException { - return doGetApplicationWindow(new HttpServletRequestWrapper(request), - new AbstractApplicationServletWrapper(applicationServlet), - application, assumedWindow); - } - - /** - * Calls the Window URI handler for a request and returns the - * {@link DownloadStream} returned by the handler. - * - * If the window is the main window of an application, the deprecated - * {@link Application#handleURI(java.net.URL, String)} is called first to - * handle {@link ApplicationResource}s and the window handler is only called - * if it returns null. - * - * @see AbstractCommunicationManager#handleURI(Window, Request, Response, - * Callback) - * - * @param window - * @param request - * @param response - * @param applicationServlet - * @return - */ - DownloadStream handleURI(Window window, HttpServletRequest request, - HttpServletResponse response, - AbstractApplicationServlet applicationServlet) { - return handleURI(window, new HttpServletRequestWrapper(request), - new HttpServletResponseWrapper(response), - new AbstractApplicationServletWrapper(applicationServlet)); - } - @Override - protected void unregisterPaintable(Component p) { - /* Cleanup possible receivers */ + protected void postPaint(Root root) { + super.postPaint(root); + + Application application = root.getApplication(); if (pidToNameToStreamVariable != null) { - Map<String, StreamVariable> removed = pidToNameToStreamVariable - .remove(getPaintableId(p)); - if (removed != null) { - for (String key : removed.keySet()) { - streamVariableToSeckey.remove(removed.get(key)); + Iterator<String> iterator = pidToNameToStreamVariable.keySet() + .iterator(); + while (iterator.hasNext()) { + String connectorId = iterator.next(); + if (application.getConnector(connectorId) == null) { + // Owner is no longer attached to the application + Map<String, StreamVariable> removed = pidToNameToStreamVariable + .get(connectorId); + for (String key : removed.keySet()) { + streamVariableToSeckey.remove(removed.get(key)); + } + iterator.remove(); } } } - super.unregisterPaintable(p); } @@ -359,7 +142,7 @@ public class CommunicationManager extends AbstractCommunicationManager { private Map<StreamVariable, String> streamVariableToSeckey; @Override - String getStreamVariableTargetUrl(VariableOwner owner, String name, + String getStreamVariableTargetUrl(Connector owner, String name, StreamVariable value) { /* * We will use the same APP/* URI space as ApplicationResources but @@ -373,7 +156,7 @@ public class CommunicationManager extends AbstractCommunicationManager { * NAME and PID from URI forms a key to fetch StreamVariable when * handling post */ - String paintableId = getPaintableId((Paintable) owner); + String paintableId = owner.getConnectorId(); String key = paintableId + "/" + name; if (pidToNameToStreamVariable == null) { @@ -402,13 +185,81 @@ public class CommunicationManager extends AbstractCommunicationManager { } @Override - protected void cleanStreamVariable(VariableOwner owner, String name) { + protected void cleanStreamVariable(Connector owner, String name) { Map<String, StreamVariable> nameToStreamVar = pidToNameToStreamVariable - .get(getPaintableId((Paintable) owner)); + .get(owner.getConnectorId()); nameToStreamVar.remove("name"); if (nameToStreamVar.isEmpty()) { - pidToNameToStreamVariable.remove(getPaintableId((Paintable) owner)); + pidToNameToStreamVariable.remove(owner.getConnectorId()); } } + @Override + protected BootstrapHandler createBootstrapHandler() { + return new BootstrapHandler() { + @Override + protected String getApplicationId(BootstrapContext context) { + String appUrl = getAppUri(context); + + String appId = appUrl; + if ("".equals(appUrl)) { + appId = "ROOT"; + } + appId = appId.replaceAll("[^a-zA-Z0-9]", ""); + // Add hashCode to the end, so that it is still (sort of) + // predictable, but indicates that it should not be used in CSS + // and + // such: + int hashCode = appId.hashCode(); + if (hashCode < 0) { + hashCode = -hashCode; + } + appId = appId + "-" + hashCode; + return appId; + } + + @Override + protected String getAppUri(BootstrapContext context) { + /* Fetch relative url to application */ + // don't use server and port in uri. It may cause problems with + // some + // virtual server configurations which lose the server name + Application application = context.getApplication(); + URL url = application.getURL(); + String appUrl = url.getPath(); + if (appUrl.endsWith("/")) { + appUrl = appUrl.substring(0, appUrl.length() - 1); + } + return appUrl; + } + + @Override + public String getThemeName(BootstrapContext context) { + String themeName = context.getRequest().getParameter( + AbstractApplicationServlet.URL_PARAMETER_THEME); + if (themeName == null) { + themeName = super.getThemeName(context); + } + return themeName; + } + + @Override + protected String getInitialUIDL(WrappedRequest request, Root root) + throws PaintException { + return CommunicationManager.this.getInitialUIDL(request, root); + } + }; + } + + @Override + protected InputStream getThemeResourceAsStream(Root root, String themeName, + String resource) { + WebApplicationContext context = (WebApplicationContext) root + .getApplication().getContext(); + ServletContext servletContext = context.getHttpSession() + .getServletContext(); + return servletContext.getResourceAsStream("/" + + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName + + "/" + resource); + } } diff --git a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java index 7889968e63..335067ca7a 100644 --- a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java +++ b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java @@ -16,8 +16,9 @@ import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; -import com.vaadin.terminal.Sizeable; +import com.vaadin.terminal.Sizeable.Unit; import com.vaadin.ui.AbstractOrderedLayout; +import com.vaadin.ui.AbstractSplitPanel; import com.vaadin.ui.Component; import com.vaadin.ui.ComponentContainer; import com.vaadin.ui.CustomComponent; @@ -25,9 +26,7 @@ import com.vaadin.ui.Form; import com.vaadin.ui.GridLayout; import com.vaadin.ui.GridLayout.Area; import com.vaadin.ui.Layout; -import com.vaadin.ui.OrderedLayout; import com.vaadin.ui.Panel; -import com.vaadin.ui.SplitPanel; import com.vaadin.ui.TabSheet; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; @@ -186,7 +185,7 @@ public class ComponentSizeValidator implements Serializable { clientJSON.write("{"); Component parent = component.getParent(); - String paintableId = communicationManager.getPaintableId(component); + String paintableId = component.getConnectorId(); clientJSON.print("id:\"" + paintableId + "\""); @@ -198,11 +197,7 @@ public class ComponentSizeValidator implements Serializable { AbstractOrderedLayout ol = (AbstractOrderedLayout) parent; boolean vertical = false; - if (ol instanceof OrderedLayout) { - if (((OrderedLayout) ol).getOrientation() == OrderedLayout.ORIENTATION_VERTICAL) { - vertical = true; - } - } else if (ol instanceof VerticalLayout) { + if (ol instanceof VerticalLayout) { vertical = true; } @@ -231,11 +226,7 @@ public class ComponentSizeValidator implements Serializable { AbstractOrderedLayout ol = (AbstractOrderedLayout) parent; boolean horizontal = true; - if (ol instanceof OrderedLayout) { - if (((OrderedLayout) ol).getOrientation() == OrderedLayout.ORIENTATION_VERTICAL) { - horizontal = false; - } - } else if (ol instanceof VerticalLayout) { + if (ol instanceof VerticalLayout) { horizontal = false; } @@ -323,7 +314,7 @@ public class ComponentSizeValidator implements Serializable { width += "MAIN WINDOW"; } else if (component.getWidth() >= 0) { width += "ABSOLUTE, " + component.getWidth() + " " - + Sizeable.UNIT_SYMBOLS[component.getWidthUnits()]; + + component.getWidthUnits().getSymbol(); } else { width += "UNDEFINED"; } @@ -339,7 +330,7 @@ public class ComponentSizeValidator implements Serializable { height += "MAIN WINDOW"; } else if (component.getHeight() > 0) { height += "ABSOLUTE, " + component.getHeight() + " " - + Sizeable.UNIT_SYMBOLS[component.getHeightUnits()]; + + component.getHeightUnits().getSymbol(); } else { height += "UNDEFINED"; } @@ -417,18 +408,13 @@ public class ComponentSizeValidator implements Serializable { if (parent.getHeight() < 0) { // Undefined height if (parent instanceof Window) { - Window w = (Window) parent; - if (w.getParent() == null) { - // main window is considered to have size - return true; - } + // Sub window with undefined size has a min-height + return true; } if (parent instanceof AbstractOrderedLayout) { boolean horizontal = true; - if (parent instanceof OrderedLayout) { - horizontal = ((OrderedLayout) parent).getOrientation() == OrderedLayout.ORIENTATION_HORIZONTAL; - } else if (parent instanceof VerticalLayout) { + if (parent instanceof VerticalLayout) { horizontal = false; } if (horizontal @@ -460,7 +446,7 @@ public class ComponentSizeValidator implements Serializable { } } - if (parent instanceof Panel || parent instanceof SplitPanel + if (parent instanceof Panel || parent instanceof AbstractSplitPanel || parent instanceof TabSheet || parent instanceof CustomComponent) { // height undefined, we know how how component works and no @@ -488,7 +474,7 @@ public class ComponentSizeValidator implements Serializable { } private static boolean hasRelativeHeight(Component component) { - return (component.getHeightUnits() == Sizeable.UNITS_PERCENTAGE && component + return (component.getHeightUnits() == Unit.PERCENTAGE && component .getHeight() > 0); } @@ -504,7 +490,7 @@ public class ComponentSizeValidator implements Serializable { private static boolean hasRelativeWidth(Component paintable) { return paintable.getWidth() > 0 - && paintable.getWidthUnits() == Sizeable.UNITS_PERCENTAGE; + && paintable.getWidthUnits() == Unit.PERCENTAGE; } public static boolean parentCanDefineWidth(Component component) { @@ -514,12 +500,8 @@ public class ComponentSizeValidator implements Serializable { return true; } if (parent instanceof Window) { - Window w = (Window) parent; - if (w.getParent() == null) { - // main window is considered to have size - return true; - } - + // Sub window with undefined size has a min-width + return true; } if (parent.getWidth() < 0) { @@ -528,11 +510,7 @@ public class ComponentSizeValidator implements Serializable { if (parent instanceof AbstractOrderedLayout) { AbstractOrderedLayout ol = (AbstractOrderedLayout) parent; boolean horizontal = true; - if (ol instanceof OrderedLayout) { - if (((OrderedLayout) ol).getOrientation() == OrderedLayout.ORIENTATION_VERTICAL) { - horizontal = false; - } - } else if (ol instanceof VerticalLayout) { + if (ol instanceof VerticalLayout) { horizontal = false; } @@ -567,7 +545,7 @@ public class ComponentSizeValidator implements Serializable { * the component width */ return hasNonRelativeWidthComponent((Form) parent); - } else if (parent instanceof SplitPanel + } else if (parent instanceof AbstractSplitPanel || parent instanceof TabSheet || parent instanceof CustomComponent) { // FIXME Could we use com.vaadin package name here and diff --git a/src/com/vaadin/terminal/gwt/server/Constants.java b/src/com/vaadin/terminal/gwt/server/Constants.java index e243bd7dae..7c467aa7f4 100644 --- a/src/com/vaadin/terminal/gwt/server/Constants.java +++ b/src/com/vaadin/terminal/gwt/server/Constants.java @@ -43,7 +43,6 @@ public interface Constants { static final String URL_PARAMETER_REPAINT_ALL = "repaintAll"; static final String URL_PARAMETER_THEME = "theme"; - static final String SERVLET_PARAMETER_DEBUG = "Debug"; static final String SERVLET_PARAMETER_PRODUCTION_MODE = "productionMode"; static final String SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION = "disable-xsrf-protection"; static final String SERVLET_PARAMETER_RESOURCE_CACHE_TIME = "resourceCacheTime"; diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java index 3a923c1840..d3fe5a890b 100644 --- a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -4,6 +4,7 @@ package com.vaadin.terminal.gwt.server; import java.io.PrintWriter; +import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -18,10 +19,12 @@ import com.vaadin.event.dd.TargetDetailsImpl; import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager.DragEventType; import com.vaadin.ui.Component; -public class DragAndDropService implements VariableOwner { +public class DragAndDropService implements VariableOwner, ClientConnector { private static final Logger logger = Logger .getLogger(DragAndDropService.class.getName()); @@ -177,7 +180,7 @@ public class DragAndDropService implements VariableOwner { } public boolean isEnabled() { - return true; + return isConnectorEnabled(); } public boolean isImmediate() { @@ -212,4 +215,27 @@ public class DragAndDropService implements VariableOwner { } return false; } + + public SharedState getState() { + // TODO Auto-generated method stub + return null; + } + + public String getConnectorId() { + return VDragAndDropManager.DD_SERVICE; + } + + public boolean isConnectorEnabled() { + // Drag'n'drop can't be disabled + return true; + } + + public List<ClientMethodInvocation> retrievePendingRpcCalls() { + return null; + } + + public RpcManager getRpcManager(Class<?> rpcInterface) { + // TODO Use rpc for drag'n'drop + return null; + } } diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java new file mode 100644 index 0000000000..375cce4161 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -0,0 +1,587 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.vaadin.Application; +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.communication.JsonEncoder; +import com.vaadin.ui.Component; + +/** + * Decoder for converting RPC parameters and other values from JSON in transfer + * between the client and the server and vice versa. + * + * @since 7.0 + */ +public class JsonCodec implements Serializable { + + private static Map<Class<?>, String> typeToTransportType = new HashMap<Class<?>, String>(); + + /** + * Note! This does not contain primitives. + * <p> + */ + private static Map<String, Class<?>> transportTypeToType = new HashMap<String, Class<?>>(); + + static { + registerType(String.class, JsonEncoder.VTYPE_STRING); + registerType(Connector.class, JsonEncoder.VTYPE_CONNECTOR); + registerType(Boolean.class, JsonEncoder.VTYPE_BOOLEAN); + registerType(boolean.class, JsonEncoder.VTYPE_BOOLEAN); + registerType(Integer.class, JsonEncoder.VTYPE_INTEGER); + registerType(int.class, JsonEncoder.VTYPE_INTEGER); + registerType(Float.class, JsonEncoder.VTYPE_FLOAT); + registerType(float.class, JsonEncoder.VTYPE_FLOAT); + registerType(Double.class, JsonEncoder.VTYPE_DOUBLE); + registerType(double.class, JsonEncoder.VTYPE_DOUBLE); + registerType(Long.class, JsonEncoder.VTYPE_LONG); + registerType(long.class, JsonEncoder.VTYPE_LONG); + registerType(String[].class, JsonEncoder.VTYPE_STRINGARRAY); + registerType(Object[].class, JsonEncoder.VTYPE_ARRAY); + registerType(Map.class, JsonEncoder.VTYPE_MAP); + registerType(List.class, JsonEncoder.VTYPE_LIST); + registerType(Set.class, JsonEncoder.VTYPE_SET); + } + + private static void registerType(Class<?> type, String transportType) { + typeToTransportType.put(type, transportType); + if (!type.isPrimitive()) { + transportTypeToType.put(transportType, type); + } + } + + public static boolean isInternalTransportType(String transportType) { + return transportTypeToType.containsKey(transportType); + } + + public static boolean isInternalType(Type type) { + if (type instanceof Class && ((Class<?>) type).isPrimitive()) { + // All primitive types are handled internally + return true; + } + return typeToTransportType.containsKey(getClassForType(type)); + } + + private static Class<?> getClassForType(Type type) { + if (type instanceof ParameterizedType) { + return (Class<?>) (((ParameterizedType) type).getRawType()); + } else { + return (Class<?>) type; + } + } + + public static String getTransportType(JSONArray encodedValue) + throws JSONException { + return encodedValue.getString(0); + } + + private static Class<?> getType(String transportType) { + return transportTypeToType.get(transportType); + } + + /** + * Decodes the given value and type, restricted to using only internal + * types. + * + * @param valueAndType + * @param application + * @throws JSONException + */ + @Deprecated + public static Object decodeInternalType(JSONArray valueAndType, + Application application) throws JSONException { + String transportType = getTransportType(valueAndType); + return decodeInternalType(getType(transportType), true, valueAndType, + application); + } + + public static Object decodeInternalOrCustomType(Type targetType, + JSONArray valueAndType, Application application) + throws JSONException { + if (isInternalType(targetType)) { + return decodeInternalType(targetType, false, valueAndType, + application); + } else { + return decodeCustomType(targetType, valueAndType, application); + } + } + + public static Object decodeCustomType(Type targetType, + JSONArray valueAndType, Application application) + throws JSONException { + if (isInternalType(targetType)) { + throw new JSONException("decodeCustomType cannot be used for " + + targetType + ", which is an internal type"); + } + String transportType = getCustomTransportType(getClassForType(targetType)); + String encodedTransportType = valueAndType.getString(0); + if (!transportTypesCompatible(encodedTransportType, transportType)) { + throw new JSONException("Expected a value of type " + transportType + + ", received " + encodedTransportType); + } + + // Try to decode object using fields + return decodeObject(targetType, (JSONObject) valueAndType.get(1), + application); + } + + /** + * Decodes a value that is of an internal type. + * <p> + * Ensures the encoded value is of the same type as target type. + * </p> + * <p> + * Allows restricting collections so that they must be declared using + * generics. If this is used then all objects in the collection are encoded + * using the declared type. Otherwise only internal types are allowed in + * collections. + * </p> + * + * @param targetType + * The type that should be returned by this method + * @param valueAndType + * The encoded value and type array + * @param application + * A reference to the application + * @param enforceGenericsInCollections + * true if generics should be enforce, false to only allow + * internal types in collections + * @return + * @throws JSONException + */ + public static Object decodeInternalType(Type targetType, + boolean restrictToInternalTypes, JSONArray valueAndType, + Application application) throws JSONException { + String encodedTransportType = valueAndType.getString(0); + if (!isInternalType(targetType)) { + throw new JSONException("Type " + targetType + + " is not a supported internal type."); + } + String transportType = getInternalTransportType(targetType); + if (!transportTypesCompatible(encodedTransportType, transportType)) { + throw new JSONException("Expected a value of type " + targetType + + ", received " + getType(encodedTransportType)); + } + + Object encodedJsonValue = valueAndType.get(1); + + if (JsonEncoder.VTYPE_NULL.equals(encodedTransportType)) { + return null; + } + // Collections + if (JsonEncoder.VTYPE_LIST.equals(transportType)) { + return decodeList(targetType, restrictToInternalTypes, + (JSONArray) encodedJsonValue, application); + } else if (JsonEncoder.VTYPE_SET.equals(transportType)) { + return decodeSet(targetType, restrictToInternalTypes, + (JSONArray) encodedJsonValue, application); + } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(transportType)) { + return decodeConnectorToObjectMap(targetType, + restrictToInternalTypes, (JSONObject) encodedJsonValue, + application); + } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) { + return decodeStringToObjectMap(targetType, restrictToInternalTypes, + (JSONObject) encodedJsonValue, application); + } + + // Arrays + if (JsonEncoder.VTYPE_ARRAY.equals(transportType)) { + + return decodeObjectArray(targetType, (JSONArray) encodedJsonValue, + application); + + } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(transportType)) { + return decodeStringArray((JSONArray) encodedJsonValue); + } + + // Special Vaadin types + + String stringValue = String.valueOf(encodedJsonValue); + + if (JsonEncoder.VTYPE_CONNECTOR.equals(transportType)) { + return application.getConnector(stringValue); + } + + // Standard Java types + + if (JsonEncoder.VTYPE_STRING.equals(transportType)) { + return stringValue; + } else if (JsonEncoder.VTYPE_INTEGER.equals(transportType)) { + return Integer.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_LONG.equals(transportType)) { + return Long.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_FLOAT.equals(transportType)) { + return Float.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_DOUBLE.equals(transportType)) { + return Double.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_BOOLEAN.equals(transportType)) { + return Boolean.valueOf(stringValue); + } + + throw new JSONException("Unknown type " + transportType); + } + + private static boolean transportTypesCompatible( + String encodedTransportType, String transportType) { + if (encodedTransportType == null) { + return false; + } + if (encodedTransportType.equals(transportType)) { + return true; + } + if (encodedTransportType.equals(JsonEncoder.VTYPE_NULL)) { + return true; + } + + return false; + } + + @Deprecated + private static Map<String, Object> decodeStringToObjectMap(Type targetType, + boolean restrictToInternalTypes, JSONObject jsonMap, + Application application) throws JSONException { + HashMap<String, Object> map = new HashMap<String, Object>(); + Iterator<String> it = jsonMap.keys(); + while (it.hasNext()) { + String key = it.next(); + JSONArray encodedValueAndType = jsonMap.getJSONArray(key); + Object decodedChild = decodeChild(targetType, + restrictToInternalTypes, 1, encodedValueAndType, + application); + map.put(key, decodedChild); + } + return map; + } + + @Deprecated + private static Map<Connector, Object> decodeConnectorToObjectMap( + Type targetType, boolean restrictToInternalTypes, + JSONObject jsonMap, Application application) throws JSONException { + HashMap<Connector, Object> map = new HashMap<Connector, Object>(); + Iterator<String> it = jsonMap.keys(); + while (it.hasNext()) { + String connectorId = it.next(); + Connector connector = application.getConnector(connectorId); + JSONArray encodedValueAndType = jsonMap.getJSONArray(connectorId); + Object decodedChild = decodeChild(targetType, + restrictToInternalTypes, 1, encodedValueAndType, + application); + map.put(connector, decodedChild); + } + return map; + } + + /** + * @param targetType + * @param restrictToInternalTypes + * @param typeIndex + * The index of a generic type to use to define the child type + * that should be decoded + * @param encodedValueAndType + * @param application + * @return + * @throws JSONException + */ + private static Object decodeChild(Type targetType, + boolean restrictToInternalTypes, int typeIndex, + JSONArray encodedValueAndType, Application application) + throws JSONException { + if (!restrictToInternalTypes && targetType instanceof ParameterizedType) { + Type childType = ((ParameterizedType) targetType) + .getActualTypeArguments()[typeIndex]; + // Only decode the given type + return decodeInternalOrCustomType(childType, encodedValueAndType, + application); + } else { + // Only internal types when not enforcing a given type to avoid + // security issues + return decodeInternalType(encodedValueAndType, application); + } + } + + private static String[] decodeStringArray(JSONArray jsonArray) + throws JSONException { + int length = jsonArray.length(); + List<String> tokens = new ArrayList<String>(length); + for (int i = 0; i < length; ++i) { + tokens.add(jsonArray.getString(i)); + } + return tokens.toArray(new String[tokens.size()]); + } + + private static Object[] decodeObjectArray(Type targetType, + JSONArray jsonArray, Application application) throws JSONException { + List list = decodeList(List.class, true, jsonArray, application); + return list.toArray(new Object[list.size()]); + } + + private static List<Object> decodeList(Type targetType, + boolean restrictToInternalTypes, JSONArray jsonArray, + Application application) throws JSONException { + List<Object> list = new ArrayList<Object>(); + for (int i = 0; i < jsonArray.length(); ++i) { + // each entry always has two elements: type and value + JSONArray encodedValueAndType = jsonArray.getJSONArray(i); + Object decodedChild = decodeChild(targetType, + restrictToInternalTypes, 0, encodedValueAndType, + application); + list.add(decodedChild); + } + return list; + } + + private static Set<Object> decodeSet(Type targetType, + boolean restrictToInternalTypes, JSONArray jsonArray, + Application application) throws JSONException { + HashSet<Object> set = new HashSet<Object>(); + set.addAll(decodeList(List.class, restrictToInternalTypes, jsonArray, + application)); + return set; + } + + /** + * Returns the name that should be used as field name in the JSON. We strip + * "set" from the setter, keeping the result - this is easy to do on both + * server and client, avoiding some issues with cASE. E.g setZIndex() + * becomes "ZIndex". Also ensures that both getter and setter are present, + * returning null otherwise. + * + * @param pd + * @return the name to be used or null if both getter and setter are not + * found. + */ + private static String getTransportFieldName(PropertyDescriptor pd) { + if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { + return null; + } + return pd.getWriteMethod().getName().substring(3); + } + + private static Object decodeObject(Type targetType, + JSONObject serializedObject, Application application) + throws JSONException { + + Class<?> targetClass = getClassForType(targetType); + try { + Object decodedObject = targetClass.newInstance(); + for (PropertyDescriptor pd : Introspector.getBeanInfo(targetClass) + .getPropertyDescriptors()) { + + String fieldName = getTransportFieldName(pd); + if (fieldName == null) { + continue; + } + JSONArray encodedFieldValue = serializedObject + .getJSONArray(fieldName); + Type fieldType = pd.getReadMethod().getGenericReturnType(); + Object decodedFieldValue = decodeInternalOrCustomType( + fieldType, encodedFieldValue, application); + + pd.getWriteMethod().invoke(decodedObject, decodedFieldValue); + } + + return decodedObject; + } catch (IllegalArgumentException e) { + throw new JSONException(e); + } catch (IllegalAccessException e) { + throw new JSONException(e); + } catch (InvocationTargetException e) { + throw new JSONException(e); + } catch (InstantiationException e) { + throw new JSONException(e); + } catch (IntrospectionException e) { + throw new JSONException(e); + } + } + + @Deprecated + private static JSONArray encode(Object value, Application application) + throws JSONException { + return encode(value, null, application); + } + + public static JSONArray encode(Object value, Class<?> valueType, + Application application) throws JSONException { + + if (null == value) { + return encodeNull(); + } + + if (valueType == null) { + valueType = value.getClass(); + } + + String internalTransportType = getInternalTransportType(valueType); + if (value instanceof String[]) { + String[] array = (String[]) value; + JSONArray jsonArray = new JSONArray(); + for (int i = 0; i < array.length; ++i) { + jsonArray.put(array[i]); + } + return combineTypeAndValue(JsonEncoder.VTYPE_STRINGARRAY, jsonArray); + } else if (value instanceof String) { + return combineTypeAndValue(JsonEncoder.VTYPE_STRING, value); + } else if (value instanceof Boolean) { + return combineTypeAndValue(JsonEncoder.VTYPE_BOOLEAN, value); + } else if (value instanceof Number) { + return combineTypeAndValue(internalTransportType, value); + } else if (value instanceof Collection) { + if (internalTransportType == null) { + throw new RuntimeException( + "Unable to serialize unsupported type: " + valueType); + } + Collection<?> collection = (Collection<?>) value; + JSONArray jsonArray = encodeCollection(collection, application); + + return combineTypeAndValue(internalTransportType, jsonArray); + } else if (value instanceof Object[]) { + Object[] array = (Object[]) value; + JSONArray jsonArray = encodeArrayContents(array, application); + return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); + } else if (value instanceof Map) { + Map<Object, Object> map = (Map<Object, Object>) value; + JSONObject jsonMap = encodeMapContents(map, application); + // Hack to support Connector as map key. Should be fixed by # + if (!map.isEmpty() + && map.keySet().iterator().next() instanceof Connector) { + return combineTypeAndValue(JsonEncoder.VTYPE_MAP_CONNECTOR, + jsonMap); + } else { + return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap); + } + } else if (value instanceof Connector) { + Connector connector = (Connector) value; + if (value instanceof Component + && !(AbstractCommunicationManager + .isVisible((Component) value))) { + return encodeNull(); + } + return combineTypeAndValue(JsonEncoder.VTYPE_CONNECTOR, + connector.getConnectorId()); + } else if (internalTransportType != null) { + return combineTypeAndValue(internalTransportType, + String.valueOf(value)); + } else { + // Any object that we do not know how to encode we encode by looping + // through fields + return combineTypeAndValue(getCustomTransportType(valueType), + encodeObject(value, application)); + } + } + + private static JSONArray encodeNull() { + return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL); + } + + private static Object encodeObject(Object value, Application application) + throws JSONException { + JSONObject jsonMap = new JSONObject(); + + try { + for (PropertyDescriptor pd : Introspector.getBeanInfo( + value.getClass()).getPropertyDescriptors()) { + Class<?> fieldType = pd.getPropertyType(); + String fieldName = getTransportFieldName(pd); + if (fieldName == null) { + continue; + } + Method getterMethod = pd.getReadMethod(); + Object fieldValue = getterMethod.invoke(value, (Object[]) null); + jsonMap.put(fieldName, + encode(fieldValue, fieldType, application)); + } + } catch (Exception e) { + // TODO: Should exceptions be handled in a different way? + throw new JSONException(e); + } + return jsonMap; + } + + private static JSONArray encodeArrayContents(Object[] array, + Application application) throws JSONException { + JSONArray jsonArray = new JSONArray(); + for (Object o : array) { + jsonArray.put(encode(o, null, application)); + } + return jsonArray; + } + + private static JSONArray encodeCollection(Collection collection, + Application application) throws JSONException { + JSONArray jsonArray = new JSONArray(); + for (Object o : collection) { + jsonArray.put(encode(o, application)); + } + return jsonArray; + } + + private static JSONObject encodeMapContents(Map<Object, Object> map, + Application application) throws JSONException { + JSONObject jsonMap = new JSONObject(); + for (Object mapKey : map.keySet()) { + Object mapValue = map.get(mapKey); + + if (mapKey instanceof ClientConnector) { + mapKey = ((ClientConnector) mapKey).getConnectorId(); + } + if (!(mapKey instanceof String)) { + throw new JSONException( + "Only maps with String/Connector keys are currently supported (#8602)"); + } + + jsonMap.put((String) mapKey, encode(mapValue, null, application)); + } + return jsonMap; + } + + private static JSONArray combineTypeAndValue(String type, Object value) { + if (type == null) { + throw new RuntimeException("Type for value " + value + + " cannot be null!"); + } + JSONArray outerArray = new JSONArray(); + outerArray.put(type); + outerArray.put(value); + return outerArray; + } + + /** + * Gets the transport type for the given class. Returns null if no transport + * type can be found. + * + * @param valueType + * The type that should be transported + * @return + * @throws JSONException + */ + private static String getInternalTransportType(Type valueType) { + return typeToTransportType.get(getClassForType(valueType)); + } + + private static String getCustomTransportType(Class<?> targetType) { + return targetType.getName(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java index d6c53a2da6..0140c0f799 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java +++ b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java @@ -4,23 +4,15 @@ package com.vaadin.terminal.gwt.server; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Serializable; -import java.io.StringWriter; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.Vector; -import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.Application; @@ -28,14 +20,12 @@ import com.vaadin.terminal.ApplicationResource; import com.vaadin.terminal.ExternalResource; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.Paintable; import com.vaadin.terminal.Resource; import com.vaadin.terminal.StreamVariable; import com.vaadin.terminal.ThemeResource; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.ui.Alignment; -import com.vaadin.ui.ClientWidget; -import com.vaadin.ui.Component; import com.vaadin.ui.CustomLayout; /** @@ -63,6 +53,10 @@ public class JsonPaintTarget implements PaintTarget { private final Stack<JsonTag> openJsonTags; + // these match each other element-wise + private final Stack<ClientConnector> openPaintables; + private final Stack<String> openPaintableTags; + private final PrintWriter uidlBuffer; private boolean closed = false; @@ -77,20 +71,13 @@ public class JsonPaintTarget implements PaintTarget { private JsonTag tag; - private int errorsOpen; - private boolean cacheEnabled = false; - private final Collection<Paintable> paintedComponents = new HashSet<Paintable>(); - - private Collection<Paintable> identifiersCreatedDueRefPaint; - - private final Collection<Class<? extends Paintable>> usedPaintableTypes = new LinkedList<Class<? extends Paintable>>(); + private final Set<Class<? extends ClientConnector>> usedClientConnectors = new HashSet<Class<? extends ClientConnector>>(); /** - * Creates a new XMLPrintWriter, without automatic line flushing. + * Creates a new JsonPaintTarget. * - * @param variableMap * @param manager * @param outWriter * A character-output stream. @@ -112,6 +99,10 @@ public class JsonPaintTarget implements PaintTarget { // Initialize tag-writing mOpenTags = new Stack<String>(); openJsonTags = new Stack<JsonTag>(); + + openPaintables = new Stack<ClientConnector>(); + openPaintableTags = new Stack<String>(); + cacheEnabled = cachingRequired; } @@ -155,10 +146,6 @@ public class JsonPaintTarget implements PaintTarget { tag = new JsonTag(tagName); - if ("error".equals(tagName)) { - errorsOpen++; - } - customLayoutArgumentsOpen = false; } @@ -197,19 +184,7 @@ public class JsonPaintTarget implements PaintTarget { + tagName + "' expected: '" + lastTag + "'."); } - // simple hack which writes error uidl structure into attribute - if ("error".equals(lastTag)) { - if (errorsOpen == 1) { - parent.addAttribute("\"error\":[\"error\",{}" - + tag.getData() + "]"); - } else { - // sub error - parent.addData(tag.getJSON()); - } - errorsOpen--; - } else { - parent.addData(tag.getJSON()); - } + parent.addData(tag.getJSON()); tag = parent; } else { @@ -424,9 +399,9 @@ public class JsonPaintTarget implements PaintTarget { } - public void addAttribute(String name, Paintable value) + public void addAttribute(String name, ClientConnector value) throws PaintException { - final String id = getPaintIdentifier(value); + final String id = value.getConnectorId(); addAttribute(name, id); } @@ -442,9 +417,8 @@ public class JsonPaintTarget implements PaintTarget { Object key = it.next(); Object mapValue = value.get(key); sb.append("\""); - if (key instanceof Paintable) { - Paintable paintable = (Paintable) key; - sb.append(getPaintIdentifier(paintable)); + if (key instanceof ClientConnector) { + sb.append(((ClientConnector) key).getConnectorId()); } else { sb.append(escapeJSON(key.toString())); } @@ -493,10 +467,9 @@ public class JsonPaintTarget implements PaintTarget { tag.addVariable(new StringVariable(owner, name, escapeJSON(value))); } - public void addVariable(VariableOwner owner, String name, Paintable value) - throws PaintException { - tag.addVariable(new StringVariable(owner, name, - getPaintIdentifier(value))); + public void addVariable(VariableOwner owner, String name, + ClientConnector value) throws PaintException { + tag.addVariable(new StringVariable(owner, name, value.getConnectorId())); } public void addVariable(VariableOwner owner, String name, int value) @@ -669,41 +642,48 @@ public class JsonPaintTarget implements PaintTarget { /* * (non-Javadoc) * - * @see com.vaadin.terminal.PaintTarget#startTag(com.vaadin.terminal + * @see com.vaadin.terminal.PaintTarget#startPaintable(com.vaadin.terminal * .Paintable, java.lang.String) */ - public boolean startTag(Paintable paintable, String tagName) + public PaintStatus startPaintable(ClientConnector connector, String tagName) throws PaintException { + boolean topLevelPaintable = openPaintables.isEmpty(); + + logger.fine("startPaintable for " + connector.getClass().getName() + + "@" + Integer.toHexString(connector.hashCode())); startTag(tagName, true); - final boolean isPreviouslyPainted = manager.hasPaintableId(paintable) - && (identifiersCreatedDueRefPaint == null || !identifiersCreatedDueRefPaint - .contains(paintable)); - final String id = manager.getPaintableId(paintable); - paintable.addListener(manager); - addAttribute("id", id); - paintedComponents.add(paintable); - - if (paintable instanceof CustomLayout) { - customLayoutArgumentsOpen = true; + + openPaintables.push(connector); + openPaintableTags.push(tagName); + + addAttribute("id", connector.getConnectorId()); + + // Only paint top level paintables. All sub paintables are marked as + // queued and painted separately later. + if (!topLevelPaintable) { + return PaintStatus.CACHED; } - return cacheEnabled && isPreviouslyPainted; + if (connector instanceof CustomLayout) { + customLayoutArgumentsOpen = true; + } + return PaintStatus.PAINTING; } - @Deprecated - public void paintReference(Paintable paintable, String referenceName) - throws PaintException { - addAttribute(referenceName, paintable); - } + public void endPaintable(ClientConnector paintable) throws PaintException { + logger.fine("endPaintable for " + paintable.getClass().getName() + "@" + + Integer.toHexString(paintable.hashCode())); - public String getPaintIdentifier(Paintable paintable) throws PaintException { - if (!manager.hasPaintableId(paintable)) { - if (identifiersCreatedDueRefPaint == null) { - identifiersCreatedDueRefPaint = new HashSet<Paintable>(); - } - identifiersCreatedDueRefPaint.add(paintable); + ClientConnector openPaintable = openPaintables.peek(); + if (paintable != openPaintable) { + throw new PaintException("Invalid UIDL: closing wrong paintable: '" + + paintable.getConnectorId() + "' expected: '" + + openPaintable.getConnectorId() + "'."); } - return manager.getPaintableId(paintable); + // remove paintable from the stack + openPaintables.pop(); + String openTag = openPaintableTags.pop(); + endTag(openTag); } /* @@ -980,178 +960,32 @@ public class JsonPaintTarget implements PaintTarget { return usedResources; } - /** - * Method to check if paintable is already painted into this target. - * - * @param p - * @return true if is not yet painted into this target and is connected to - * app - */ - public boolean needsToBePainted(Paintable p) { - if (paintedComponents.contains(p)) { - return false; - } else if (((Component) p).getApplication() == null) { - return false; - } else { - return true; - } - } - - private static final Map<Class<? extends Paintable>, Class<? extends Paintable>> widgetMappingCache = new HashMap<Class<? extends Paintable>, Class<? extends Paintable>>(); - @SuppressWarnings("unchecked") - public String getTag(Paintable paintable) { - Class<? extends Paintable> class1; - synchronized (widgetMappingCache) { - class1 = widgetMappingCache.get(paintable.getClass()); + public String getTag(ClientConnector clientConnector) { + Class<? extends ClientConnector> clientConnectorClass = clientConnector + .getClass(); + while (clientConnectorClass.isAnonymousClass()) { + clientConnectorClass = (Class<? extends ClientConnector>) clientConnectorClass + .getSuperclass(); } - if (class1 == null) { - /* - * Client widget annotation is searched from component hierarchy to - * detect the component that presumably has client side - * implementation. The server side name is used in the - * transportation, but encoded into integer strings to optimized - * transferred data. - */ - class1 = paintable.getClass(); - while (!hasClientWidgetMapping(class1)) { - Class<?> superclass = class1.getSuperclass(); - if (superclass != null - && Paintable.class.isAssignableFrom(superclass)) { - class1 = (Class<? extends Paintable>) superclass; - } else { - logger.warning("No superclass of " - + paintable.getClass().getName() - + " has a @ClientWidget" - + " annotation. Component will not be mapped correctly on client side."); - break; - } - } - synchronized (widgetMappingCache) { - widgetMappingCache.put(paintable.getClass(), class1); - } - } - - usedPaintableTypes.add(class1); - return manager.getTagForType(class1); - - } - - private boolean hasClientWidgetMapping(Class<? extends Paintable> class1) { - try { - return class1.isAnnotationPresent(ClientWidget.class); - } catch (NoClassDefFoundError e) { - String stacktrace = getStacktraceString(e); - if (stacktrace - .contains("com.ibm.oti.reflect.AnnotationParser.parseClass")) { - // #7479 IBM JVM apparently tries to eagerly load the classes - // referred to by annotations. Checking the annotation from byte - // code to be sure that we are dealing the this case and not - // some other class loading issue. - if (bytecodeContainsClientWidgetAnnotation(class1)) { - return true; - } - } else { - // throw exception forward - throw e; - } - } catch (LinkageError e) { - String stacktrace = getStacktraceString(e); - if (stacktrace - .contains("org.jboss.modules.ModuleClassLoader.defineClass")) { - // #7822 JBoss AS 7 apparently tries to eagerly load the classes - // referred to by annotations. Checking the annotation from byte - // code to be sure that we are dealing the this case and not - // some other class loading issue. - if (bytecodeContainsClientWidgetAnnotation(class1)) { - // Seems that JBoss still prints a stacktrace to the logs - // even though the LinkageError has been caught - return true; - } - } else { - // throw exception forward - throw e; - } - } catch (RuntimeException e) { - if (e.getStackTrace()[0].getClassName().equals( - "org.glassfish.web.loader.WebappClassLoader")) { - - // See #3920 - // Glassfish 3 is darn eager to load the value class, even - // though we just want to check if the annotation exists. - - // In some situations (depending on class loading order) it - // would be enough to return true here, but it is safer to check - // the annotation from byte code - - if (bytecodeContainsClientWidgetAnnotation(class1)) { - return true; - } - } else { - // throw exception forward - throw e; - } - } - return false; - } - - private static String getStacktraceString(Throwable e) { - StringWriter writer = new StringWriter(); - e.printStackTrace(new PrintWriter(writer)); - String stacktrace = writer.toString(); - return stacktrace; - } - - private boolean bytecodeContainsClientWidgetAnnotation( - Class<? extends Paintable> class1) { - - try { - String name = class1.getName().replace('.', '/') + ".class"; - - InputStream stream = class1.getClassLoader().getResourceAsStream( - name); - BufferedReader bufferedReader = new BufferedReader( - new InputStreamReader(stream)); - try { - String line; - boolean atSourcefile = false; - while ((line = bufferedReader.readLine()) != null) { - if (line.startsWith("SourceFile")) { - atSourcefile = true; - } - if (atSourcefile) { - if (line.contains("ClientWidget")) { - return true; - } - } - // TODO could optimize to quit at the end attribute - } - } catch (IOException e1) { - logger.log(Level.SEVERE, - "An error occurred while finding widget mapping.", e1); - } finally { - try { - bufferedReader.close(); - } catch (IOException e1) { - logger.log(Level.SEVERE, "Could not close reader.", e1); - - } - } - } catch (Throwable t) { - logger.log(Level.SEVERE, - "An error occurred while finding widget mapping.", t); + Class<?> clazz = clientConnectorClass; + while (!usedClientConnectors.contains(clazz) + && clazz.getSuperclass() != null + && ClientConnector.class.isAssignableFrom(clazz)) { + usedClientConnectors.add((Class<? extends ClientConnector>) clazz); + clazz = clazz.getSuperclass(); } - - return false; + return manager.getTagForType(clientConnectorClass); } - Collection<Class<? extends Paintable>> getUsedPaintableTypes() { - return usedPaintableTypes; + Collection<Class<? extends ClientConnector>> getUsedClientConnectors() { + return usedClientConnectors; } public void addVariable(VariableOwner owner, String name, StreamVariable value) throws PaintException { - String url = manager.getStreamVariableTargetUrl(owner, name, value); + String url = manager.getStreamVariableTargetUrl((Connector) owner, + name, value); if (url != null) { addVariable(owner, name, url); } // else { //NOP this was just a cleanup by component } diff --git a/src/com/vaadin/terminal/gwt/server/LegacyChangeVariablesInvocation.java b/src/com/vaadin/terminal/gwt/server/LegacyChangeVariablesInvocation.java new file mode 100644 index 0000000000..42fa3ab5a5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/LegacyChangeVariablesInvocation.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.server; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; + +public class LegacyChangeVariablesInvocation extends MethodInvocation { + private Map<String, Object> variableChanges = new HashMap<String, Object>(); + + public LegacyChangeVariablesInvocation(String connectorId, + String variableName, Object value) { + super(connectorId, ApplicationConnection.UPDATE_VARIABLE_INTERFACE, + ApplicationConnection.UPDATE_VARIABLE_METHOD); + setVariableChange(variableName, value); + } + + public static boolean isLegacyVariableChange(String interfaceName, + String methodName) { + return ApplicationConnection.UPDATE_VARIABLE_METHOD + .equals(interfaceName) + && ApplicationConnection.UPDATE_VARIABLE_METHOD + .equals(methodName); + } + + public void setVariableChange(String name, Object value) { + variableChanges.put(name, value); + } + + public Map<String, Object> getVariableChanges() { + return variableChanges; + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext.java b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext.java deleted file mode 100644 index 362fee1cc7..0000000000 --- a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext.java +++ /dev/null @@ -1,186 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -/** - * - */ -package com.vaadin.terminal.gwt.server; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import javax.portlet.ActionRequest; -import javax.portlet.ActionResponse; -import javax.portlet.Portlet; -import javax.portlet.PortletSession; -import javax.portlet.RenderRequest; -import javax.portlet.RenderResponse; -import javax.servlet.http.HttpSession; - -import com.vaadin.Application; - -/** - * @author marc - */ -public class PortletApplicationContext extends WebApplicationContext implements - Serializable { - - protected transient PortletSession portletSession; - - protected Map<Application, Set<PortletListener>> portletListeners = new HashMap<Application, Set<PortletListener>>(); - - protected Map<Portlet, Application> portletToApplication = new HashMap<Portlet, Application>(); - - PortletApplicationContext() { - - } - - static public PortletApplicationContext getApplicationContext( - PortletSession session) { - WebApplicationContext cx = (WebApplicationContext) session - .getAttribute(WebApplicationContext.class.getName(), - PortletSession.APPLICATION_SCOPE); - if (cx == null) { - cx = new PortletApplicationContext(); - } - if (!(cx instanceof PortletApplicationContext)) { - // TODO Should we even try this? And should we leave original as-is? - PortletApplicationContext pcx = new PortletApplicationContext(); - pcx.applications.addAll(cx.applications); - cx.applications.clear(); - pcx.browser = cx.browser; - cx.browser = null; - pcx.listeners = cx.listeners; - cx.listeners = null; - pcx.session = cx.session; - cx = pcx; - } - if (((PortletApplicationContext) cx).portletSession == null) { - ((PortletApplicationContext) cx).portletSession = session; - } - session.setAttribute(WebApplicationContext.class.getName(), cx, - PortletSession.APPLICATION_SCOPE); - return (PortletApplicationContext) cx; - } - - static public WebApplicationContext getApplicationContext( - HttpSession session) { - WebApplicationContext cx = (WebApplicationContext) session - .getAttribute(WebApplicationContext.class.getName()); - if (cx == null) { - cx = new PortletApplicationContext(); - } - if (cx.session == null) { - cx.session = session; - } - session.setAttribute(WebApplicationContext.class.getName(), cx); - return cx; - } - - @Override - protected void removeApplication(Application application) { - portletListeners.remove(application); - for (Iterator<Application> it = portletToApplication.values() - .iterator(); it.hasNext();) { - Application value = it.next(); - if (value == application) { - // values().iterator() is backed by the map - it.remove(); - } - } - super.removeApplication(application); - } - - /** - * Reinitializing the session is not supported from portlets. - * - * @see com.vaadin.terminal.gwt.server.WebApplicationContext#reinitializeSession() - */ - @Override - public void reinitializeSession() { - throw new UnsupportedOperationException( - "Reinitializing the session is not supported from portlets"); - } - - public void setPortletApplication(Portlet portlet, Application app) { - portletToApplication.put(portlet, app); - } - - public Application getPortletApplication(Portlet portlet) { - return portletToApplication.get(portlet); - } - - public PortletSession getPortletSession() { - return portletSession; - } - - public void addPortletListener(Application app, PortletListener listener) { - Set<PortletListener> l = portletListeners.get(app); - if (l == null) { - l = new LinkedHashSet<PortletListener>(); - portletListeners.put(app, l); - } - l.add(listener); - } - - public void removePortletListener(Application app, PortletListener listener) { - Set<PortletListener> l = portletListeners.get(app); - if (l != null) { - l.remove(listener); - } - } - - public static void dispatchRequest(Portlet portlet, RenderRequest request, - RenderResponse response) { - PortletApplicationContext ctx = getApplicationContext(request - .getPortletSession()); - if (ctx != null) { - ctx.firePortletRenderRequest(portlet, request, response); - } - } - - public static void dispatchRequest(Portlet portlet, ActionRequest request, - ActionResponse response) { - PortletApplicationContext ctx = getApplicationContext(request - .getPortletSession()); - if (ctx != null) { - ctx.firePortletActionRequest(portlet, request, response); - } - } - - public void firePortletRenderRequest(Portlet portlet, - RenderRequest request, RenderResponse response) { - Application app = getPortletApplication(portlet); - Set<PortletListener> listeners = portletListeners.get(app); - if (listeners != null) { - for (PortletListener l : listeners) { - l.handleRenderRequest(request, new RestrictedRenderResponse( - response)); - } - } - } - - public void firePortletActionRequest(Portlet portlet, - ActionRequest request, ActionResponse response) { - Application app = getPortletApplication(portlet); - Set<PortletListener> listeners = portletListeners.get(app); - if (listeners != null) { - for (PortletListener l : listeners) { - l.handleActionRequest(request, response); - } - } - } - - public interface PortletListener extends Serializable { - public void handleRenderRequest(RenderRequest request, - RenderResponse response); - - public void handleActionRequest(ActionRequest request, - ActionResponse response); - } - -} diff --git a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java index dce1a1a78c..661da57af6 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java +++ b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java @@ -35,8 +35,7 @@ import javax.xml.namespace.QName; import com.vaadin.Application; import com.vaadin.terminal.ApplicationResource; -import com.vaadin.terminal.ExternalResource; -import com.vaadin.ui.Window; +import com.vaadin.ui.Root; /** * TODO Write documentation, fix JavaDoc tags. @@ -172,18 +171,18 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { } } - public void firePortletRenderRequest(Application app, Window window, + public void firePortletRenderRequest(Application app, Root root, RenderRequest request, RenderResponse response) { Set<PortletListener> listeners = portletListeners.get(app); if (listeners != null) { for (PortletListener l : listeners) { l.handleRenderRequest(request, new RestrictedRenderResponse( - response), window); + response), root); } } } - public void firePortletActionRequest(Application app, Window window, + public void firePortletActionRequest(Application app, Root root, ActionRequest request, ActionResponse response) { String key = request.getParameter(ActionRequest.ACTION_NAME); if (eventActionDestinationMap.containsKey(key)) { @@ -205,28 +204,28 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { Set<PortletListener> listeners = portletListeners.get(app); if (listeners != null) { for (PortletListener l : listeners) { - l.handleActionRequest(request, response, window); + l.handleActionRequest(request, response, root); } } } } - public void firePortletEventRequest(Application app, Window window, + public void firePortletEventRequest(Application app, Root root, EventRequest request, EventResponse response) { Set<PortletListener> listeners = portletListeners.get(app); if (listeners != null) { for (PortletListener l : listeners) { - l.handleEventRequest(request, response, window); + l.handleEventRequest(request, response, root); } } } - public void firePortletResourceRequest(Application app, Window window, + public void firePortletResourceRequest(Application app, Root root, ResourceRequest request, ResourceResponse response) { Set<PortletListener> listeners = portletListeners.get(app); if (listeners != null) { for (PortletListener l : listeners) { - l.handleResourceRequest(request, response, window); + l.handleResourceRequest(request, response, root); } } } @@ -234,16 +233,16 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { public interface PortletListener extends Serializable { public void handleRenderRequest(RenderRequest request, - RenderResponse response, Window window); + RenderResponse response, Root root); public void handleActionRequest(ActionRequest request, - ActionResponse response, Window window); + ActionResponse response, Root root); public void handleEventRequest(EventRequest request, - EventResponse response, Window window); + EventResponse response, Root root); public void handleResourceRequest(ResourceRequest request, - ResourceResponse response, Window window); + ResourceResponse response, Root root); } /** @@ -308,7 +307,7 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { * Event names for events sent and received by a portlet need to be declared * in portlet.xml . * - * @param window + * @param root * a window in which a temporary action URL can be opened if * necessary * @param name @@ -317,7 +316,7 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { * event value object that is Serializable and, if appropriate, * has a valid JAXB annotation */ - public void sendPortletEvent(Window window, QName name, Serializable value) + public void sendPortletEvent(Root root, QName name, Serializable value) throws IllegalStateException { if (response instanceof MimeResponse) { String actionKey = "" + System.currentTimeMillis(); @@ -328,7 +327,9 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { if (actionUrl != null) { eventActionDestinationMap.put(actionKey, name); eventActionValueMap.put(actionKey, value); - window.open(new ExternalResource(actionUrl.toString())); + throw new RuntimeException( + "Root.open has not yet been implemented"); + // root.open(new ExternalResource(actionUrl.toString())); } else { // this should never happen as we already know the response is a // MimeResponse @@ -355,7 +356,7 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { * Shared parameters set or read by a portlet need to be declared in * portlet.xml . * - * @param window + * @param root * a window in which a temporary action URL can be opened if * necessary * @param name @@ -363,8 +364,8 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { * @param value * parameter value */ - public void setSharedRenderParameter(Window window, String name, - String value) throws IllegalStateException { + public void setSharedRenderParameter(Root root, String name, String value) + throws IllegalStateException { if (response instanceof MimeResponse) { String actionKey = "" + System.currentTimeMillis(); while (sharedParameterActionNameMap.containsKey(actionKey)) { @@ -374,7 +375,9 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { if (actionUrl != null) { sharedParameterActionNameMap.put(actionKey, name); sharedParameterActionValueMap.put(actionKey, value); - window.open(new ExternalResource(actionUrl.toString())); + throw new RuntimeException( + "Root.open has not yet been implemented"); + // root.open(new ExternalResource(actionUrl.toString())); } else { // this should never happen as we already know the response is a // MimeResponse @@ -394,7 +397,7 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { * * Portlet modes used by a portlet need to be declared in portlet.xml . * - * @param window + * @param root * a window in which the render URL can be opened if necessary * @param portletMode * the portlet mode to switch to @@ -402,12 +405,13 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { * if the portlet mode is not allowed for some reason * (configuration, permissions etc.) */ - public void setPortletMode(Window window, PortletMode portletMode) + public void setPortletMode(Root root, PortletMode portletMode) throws IllegalStateException, PortletModeException { if (response instanceof MimeResponse) { PortletURL url = ((MimeResponse) response).createRenderURL(); url.setPortletMode(portletMode); - window.open(new ExternalResource(url.toString())); + throw new RuntimeException("Root.open has not yet been implemented"); + // root.open(new ExternalResource(url.toString())); } else if (response instanceof StateAwareResponse) { ((StateAwareResponse) response).setPortletMode(portletMode); } else { diff --git a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java index ffa8ad4054..b3ec33a9e0 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java @@ -5,29 +5,29 @@ package com.vaadin.terminal.gwt.server; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Method; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; -import javax.portlet.ClientDataRequest; import javax.portlet.MimeResponse; +import javax.portlet.PortletContext; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; -import javax.portlet.PortletSession; -import javax.portlet.ResourceRequest; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; import javax.portlet.ResourceResponse; import javax.portlet.ResourceURL; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequestWrapper; import com.vaadin.Application; -import com.vaadin.terminal.DownloadStream; -import com.vaadin.terminal.Paintable; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.PaintException; import com.vaadin.terminal.StreamVariable; -import com.vaadin.terminal.VariableOwner; -import com.vaadin.ui.Component; -import com.vaadin.ui.Window; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.ui.Root; /** * TODO document me! @@ -40,241 +40,65 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { private transient ResourceResponse currentUidlResponse; - private static class PortletRequestWrapper implements Request { - - private final PortletRequest request; - - public PortletRequestWrapper(PortletRequest request) { - this.request = request; - } - - public Object getAttribute(String name) { - return request.getAttribute(name); - } - - public int getContentLength() { - return ((ClientDataRequest) request).getContentLength(); - } - - public InputStream getInputStream() throws IOException { - return ((ClientDataRequest) request).getPortletInputStream(); - } - - public String getParameter(String name) { - String value = request.getParameter(name); - if (value == null) { - // for GateIn portlet container simple-portal - try { - Method getRealReq = request.getClass().getMethod( - "getRealRequest"); - HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq - .invoke(request); - value = origRequest.getParameter(name); - } catch (Exception e) { - // do nothing - not on GateIn simple-portal - } - } - return value; - } - - public String getRequestID() { - return "WindowID:" + request.getWindowID(); - } - - public Session getSession() { - return new PortletSessionWrapper(request.getPortletSession()); - } - - public Object getWrappedRequest() { - return request; - } - - public boolean isRunningInPortlet() { - return true; - } - - public void setAttribute(String name, Object o) { - request.setAttribute(name, o); - } - - } - - private static class PortletResponseWrapper implements Response { - - private final PortletResponse response; - - public PortletResponseWrapper(PortletResponse response) { - this.response = response; - } - - public OutputStream getOutputStream() throws IOException { - return ((MimeResponse) response).getPortletOutputStream(); - } - - public Object getWrappedResponse() { - return response; - } - - public void setContentType(String type) { - ((MimeResponse) response).setContentType(type); - } - } - - private static class PortletSessionWrapper implements Session { - - private final PortletSession session; - - public PortletSessionWrapper(PortletSession session) { - this.session = session; - } - - public Object getAttribute(String name) { - return session.getAttribute(name); - } - - public int getMaxInactiveInterval() { - return session.getMaxInactiveInterval(); - } - - public Object getWrappedSession() { - return session; - } - - public boolean isNew() { - return session.isNew(); - } - - public void setAttribute(String name, Object o) { - session.setAttribute(name, o); - } - - } - - private static class AbstractApplicationPortletWrapper implements Callback { - - private final AbstractApplicationPortlet portlet; - - public AbstractApplicationPortletWrapper( - AbstractApplicationPortlet portlet) { - this.portlet = portlet; - } - - public void criticalNotification(Request request, Response response, - String cap, String msg, String details, String outOfSyncURL) - throws IOException { - portlet.criticalNotification( - (PortletRequest) request.getWrappedRequest(), - (MimeResponse) response.getWrappedResponse(), cap, msg, - details, outOfSyncURL); - } - - public String getRequestPathInfo(Request request) { - if (request.getWrappedRequest() instanceof ResourceRequest) { - return ((ResourceRequest) request.getWrappedRequest()) - .getResourceID(); - } else { - // We do not use paths in portlet mode - throw new UnsupportedOperationException( - "PathInfo only available when using ResourceRequests"); - } - } - - public InputStream getThemeResourceAsStream(String themeName, - String resource) throws IOException { - return portlet.getPortletContext().getResourceAsStream( - "/" + AbstractApplicationPortlet.THEME_DIRECTORY_PATH - + themeName + "/" + resource); - } - - } - public PortletCommunicationManager(Application application) { super(application); } - public void handleFileUpload(ResourceRequest request, - ResourceResponse response) throws IOException { + public void handleFileUpload(WrappedRequest request, + WrappedResponse response) throws IOException { String contentType = request.getContentType(); String name = request.getParameter("name"); String ownerId = request.getParameter("rec-owner"); - VariableOwner variableOwner = getVariableOwner(ownerId); - StreamVariable streamVariable = ownerToNameToStreamVariable.get( - variableOwner).get(name); + Connector owner = getConnector(getApplication(), ownerId); + StreamVariable streamVariable = ownerToNameToStreamVariable.get(owner) + .get(name); if (contentType.contains("boundary")) { - doHandleSimpleMultipartFileUpload( - new PortletRequestWrapper(request), - new PortletResponseWrapper(response), streamVariable, name, - variableOwner, contentType.split("boundary=")[1]); + doHandleSimpleMultipartFileUpload(request, response, + streamVariable, name, owner, + contentType.split("boundary=")[1]); } else { - doHandleXhrFilePost(new PortletRequestWrapper(request), - new PortletResponseWrapper(response), streamVariable, name, - variableOwner, request.getContentLength()); + doHandleXhrFilePost(request, response, streamVariable, name, owner, + request.getContentLength()); } } @Override - protected void unregisterPaintable(Component p) { - super.unregisterPaintable(p); + protected void postPaint(Root root) { + super.postPaint(root); + + Application application = root.getApplication(); if (ownerToNameToStreamVariable != null) { - ownerToNameToStreamVariable.remove(p); + Iterator<Connector> iterator = ownerToNameToStreamVariable.keySet() + .iterator(); + while (iterator.hasNext()) { + Connector owner = iterator.next(); + if (application.getConnector(owner.getConnectorId()) == null) { + // Owner is no longer attached to the application + iterator.remove(); + } + } } } - public void handleUidlRequest(ResourceRequest request, - ResourceResponse response, - AbstractApplicationPortlet applicationPortlet, Window window) - throws InvalidUIDLSecurityKeyException, IOException { - currentUidlResponse = response; - doHandleUidlRequest(new PortletRequestWrapper(request), - new PortletResponseWrapper(response), - new AbstractApplicationPortletWrapper(applicationPortlet), - window); + @Override + public void handleUidlRequest(WrappedRequest request, + WrappedResponse response, Callback callback, Root root) + throws IOException, InvalidUIDLSecurityKeyException { + currentUidlResponse = (ResourceResponse) ((WrappedPortletResponse) response) + .getPortletResponse(); + super.handleUidlRequest(request, response, callback, root); currentUidlResponse = null; } - DownloadStream handleURI(Window window, ResourceRequest request, - ResourceResponse response, - AbstractApplicationPortlet applicationPortlet) { - return handleURI(window, new PortletRequestWrapper(request), - new PortletResponseWrapper(response), - new AbstractApplicationPortletWrapper(applicationPortlet)); - } - - /** - * Gets the existing application or creates a new one. Get a window within - * an application based on the requested URI. - * - * @param request - * the portlet Request. - * @param applicationPortlet - * @param application - * the Application to query for window. - * @param assumedWindow - * if the window has been already resolved once, this parameter - * must contain the window. - * @return Window matching the given URI or null if not found. - * @throws ServletException - * if an exception has occurred that interferes with the - * servlet's normal operation. - */ - Window getApplicationWindow(PortletRequest request, - AbstractApplicationPortlet applicationPortlet, - Application application, Window assumedWindow) { - - return doGetApplicationWindow(new PortletRequestWrapper(request), - new AbstractApplicationPortletWrapper(applicationPortlet), - application, assumedWindow); - } - - private Map<VariableOwner, Map<String, StreamVariable>> ownerToNameToStreamVariable; + private Map<Connector, Map<String, StreamVariable>> ownerToNameToStreamVariable; @Override - String getStreamVariableTargetUrl(VariableOwner owner, String name, + String getStreamVariableTargetUrl(Connector owner, String name, StreamVariable value) { if (ownerToNameToStreamVariable == null) { - ownerToNameToStreamVariable = new HashMap<VariableOwner, Map<String, StreamVariable>>(); + ownerToNameToStreamVariable = new HashMap<Connector, Map<String, StreamVariable>>(); } Map<String, StreamVariable> nameToReceiver = ownerToNameToStreamVariable .get(owner); @@ -286,14 +110,14 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { ResourceURL resurl = currentUidlResponse.createResourceURL(); resurl.setResourceID("UPLOAD"); resurl.setParameter("name", name); - resurl.setParameter("rec-owner", getPaintableId((Paintable) owner)); + resurl.setParameter("rec-owner", owner.getConnectorId()); resurl.setProperty("name", name); - resurl.setProperty("rec-owner", getPaintableId((Paintable) owner)); + resurl.setProperty("rec-owner", owner.getConnectorId()); return resurl.toString(); } @Override - protected void cleanStreamVariable(VariableOwner owner, String name) { + protected void cleanStreamVariable(Connector owner, String name) { Map<String, StreamVariable> map = ownerToNameToStreamVariable .get(owner); map.remove(name); @@ -302,4 +126,131 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { } } + @Override + protected BootstrapHandler createBootstrapHandler() { + return new BootstrapHandler() { + @Override + public boolean handleRequest(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException { + PortletRequest portletRequest = WrappedPortletRequest.cast( + request).getPortletRequest(); + if (portletRequest instanceof RenderRequest) { + return super.handleRequest(application, request, response); + } else { + return false; + } + } + + @Override + protected String getApplicationId(BootstrapContext context) { + PortletRequest portletRequest = WrappedPortletRequest.cast( + context.getRequest()).getPortletRequest(); + /* + * We need to generate a unique ID because some portals already + * create a DIV with the portlet's Window ID as the DOM ID. + */ + return "v-" + portletRequest.getWindowID(); + } + + @Override + protected String getAppUri(BootstrapContext context) { + return getRenderResponse(context).createActionURL().toString(); + } + + private RenderResponse getRenderResponse(BootstrapContext context) { + PortletResponse response = ((WrappedPortletResponse) context + .getResponse()).getPortletResponse(); + + RenderResponse renderResponse = (RenderResponse) response; + return renderResponse; + } + + @Override + protected JSONObject getDefaultParameters(BootstrapContext context) + throws JSONException { + /* + * We need this in order to get uploads to work. TODO this is + * not needed for uploads anymore, check if this is needed for + * some other things + */ + JSONObject defaults = super.getDefaultParameters(context); + defaults.put("usePortletURLs", true); + + ResourceURL uidlUrlBase = getRenderResponse(context) + .createResourceURL(); + uidlUrlBase.setResourceID("UIDL"); + defaults.put("portletUidlURLBase", uidlUrlBase.toString()); + defaults.put("pathInfo", ""); + + return defaults; + } + + @Override + protected void writeMainScriptTagContents(BootstrapContext context) + throws JSONException, IOException { + // fixed base theme to use - all portal pages with Vaadin + // applications will load this exactly once + String portalTheme = WrappedPortletRequest + .cast(context.getRequest()) + .getPortalProperty( + AbstractApplicationPortlet.PORTAL_PARAMETER_VAADIN_THEME); + if (portalTheme != null + && !portalTheme.equals(context.getThemeName())) { + String portalThemeUri = getThemeUri(context, portalTheme); + // XSS safe - originates from portal properties + context.getWriter().write( + "vaadin.loadTheme('" + portalThemeUri + "');"); + } + + super.writeMainScriptTagContents(context); + } + + @Override + protected String getMainDivStyle(BootstrapContext context) { + DeploymentConfiguration deploymentConfiguration = context + .getRequest().getDeploymentConfiguration(); + return deploymentConfiguration.getApplicationOrSystemProperty( + AbstractApplicationPortlet.PORTLET_PARAMETER_STYLE, + null); + } + + @Override + protected String getInitialUIDL(WrappedRequest request, Root root) + throws PaintException { + return PortletCommunicationManager.this.getInitialUIDL(request, + root); + } + + @Override + protected JSONObject getApplicationParameters( + BootstrapContext context) throws JSONException, + PaintException { + JSONObject parameters = super.getApplicationParameters(context); + WrappedPortletResponse wrappedPortletResponse = (WrappedPortletResponse) context + .getResponse(); + MimeResponse portletResponse = (MimeResponse) wrappedPortletResponse + .getPortletResponse(); + ResourceURL resourceURL = portletResponse.createResourceURL(); + resourceURL.setResourceID("browserDetails"); + parameters.put("browserDetailsUrl", resourceURL.toString()); + return parameters; + } + + }; + + } + + @Override + protected InputStream getThemeResourceAsStream(Root root, String themeName, + String resource) { + PortletApplicationContext2 context = (PortletApplicationContext2) root + .getApplication().getContext(); + PortletContext portletContext = context.getPortletSession() + .getPortletContext(); + return portletContext.getResourceAsStream("/" + + AbstractApplicationPortlet.THEME_DIRECTORY_PATH + themeName + + "/" + resource); + } + } diff --git a/src/com/vaadin/terminal/gwt/server/RequestTimer.java b/src/com/vaadin/terminal/gwt/server/RequestTimer.java index 5ed89c2d29..d47f444bef 100644 --- a/src/com/vaadin/terminal/gwt/server/RequestTimer.java +++ b/src/com/vaadin/terminal/gwt/server/RequestTimer.java @@ -4,10 +4,7 @@ package com.vaadin.terminal.gwt.server; -import javax.portlet.PortletRequest; -import javax.portlet.PortletSession; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; +import com.vaadin.terminal.WrappedRequest; /** * Times the handling of requests and stores the information as an attribute in @@ -25,72 +22,13 @@ public class RequestTimer { private long lastRequestTime = -1; /** - * This class acts as a proxy for setting and getting session and request - * attributes on HttpServletRequests and PortletRequests. Using this class - * we don't need to duplicate code everywhere. - */ - static class RequestWrapper { - private final HttpServletRequest servletRequest; - private final PortletRequest portletRequest; - - public RequestWrapper(HttpServletRequest servletRequest) { - this.servletRequest = servletRequest; - portletRequest = null; - } - - public RequestWrapper(PortletRequest portletRequest) { - this.portletRequest = portletRequest; - servletRequest = null; - } - - public void setAttribute(String name, Object value) { - if (servletRequest != null) { - servletRequest.setAttribute(name, value); - } else { - portletRequest.setAttribute(name, value); - } - } - - public void setSessionAttribute(String name, Object value) { - if (servletRequest != null) { - HttpSession session = servletRequest.getSession(); - if (session != null) { - session.setAttribute(name, value); - } - } else { - PortletSession portletSession = portletRequest - .getPortletSession(); - if (portletSession != null) { - portletSession.setAttribute(name, value); - } - } - } - - public Object getSessionAttribute(String name) { - if (servletRequest != null) { - HttpSession session = servletRequest.getSession(); - if (session != null) { - return session.getAttribute(name); - } - } else { - PortletSession portletSession = portletRequest - .getPortletSession(); - if (portletSession != null) { - return portletSession.getAttribute(name); - } - } - return null; - } - } - - /** * Starts the timing of a request. This should be called before any * processing of the request. * * @param request * the request. */ - public void start(RequestWrapper request) { + public void start(WrappedRequest request) { requestStartTime = System.nanoTime(); request.setAttribute("TOTAL", totalSessionTime); request.setAttribute("LASTREQUEST", lastRequestTime); @@ -116,7 +54,7 @@ public class RequestTimer { * the request for which to get a valid timer. * @return a valid timer. */ - public static RequestTimer get(RequestWrapper request) { + public static RequestTimer get(WrappedRequest request) { RequestTimer timer = (RequestTimer) request .getSessionAttribute(SESSION_ATTR_ID); if (timer == null) { @@ -136,7 +74,7 @@ public class RequestTimer { * @param requestTimer * the timer. */ - public static void set(RequestWrapper request, RequestTimer requestTimer) { + public static void set(WrappedRequest request, RequestTimer requestTimer) { request.setSessionAttribute(RequestTimer.SESSION_ATTR_ID, requestTimer); } } diff --git a/src/com/vaadin/terminal/gwt/server/ResourceReference.java b/src/com/vaadin/terminal/gwt/server/ResourceReference.java new file mode 100644 index 0000000000..56f2bed896 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ResourceReference.java @@ -0,0 +1,51 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.server; + +import com.vaadin.Application; +import com.vaadin.terminal.ApplicationResource; +import com.vaadin.terminal.ExternalResource; +import com.vaadin.terminal.Resource; +import com.vaadin.terminal.ThemeResource; +import com.vaadin.terminal.gwt.client.communication.URLReference; + +public class ResourceReference extends URLReference { + + private Resource resource; + + public ResourceReference(Resource resource) { + this.resource = resource; + } + + public Resource getResource() { + return resource; + } + + @Override + public String getURL() { + if (resource instanceof ExternalResource) { + return ((ExternalResource) resource).getURL(); + } else if (resource instanceof ApplicationResource) { + final ApplicationResource r = (ApplicationResource) resource; + final Application a = r.getApplication(); + if (a == null) { + throw new RuntimeException( + "An ApplicationResource (" + + r.getClass().getName() + + " must be attached to an application when it is sent to the client."); + } + final String uri = a.getRelativeLocation(r); + return uri; + } else if (resource instanceof ThemeResource) { + final String uri = "theme://" + + ((ThemeResource) resource).getResourceId(); + return uri; + } else { + throw new RuntimeException(getClass().getSimpleName() + + " does not support resources of type: " + + resource.getClass().getName()); + } + + } +} diff --git a/src/com/vaadin/terminal/gwt/server/RpcManager.java b/src/com/vaadin/terminal/gwt/server/RpcManager.java new file mode 100644 index 0000000000..d240ab8467 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/RpcManager.java @@ -0,0 +1,17 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.Serializable; + +/** + * Server side RPC manager that can invoke methods based on RPC calls received + * from the client. + * + * @since 7.0 + */ +public interface RpcManager extends Serializable { + public void applyInvocation(ServerRpcMethodInvocation invocation); +} diff --git a/src/com/vaadin/terminal/gwt/server/RpcTarget.java b/src/com/vaadin/terminal/gwt/server/RpcTarget.java new file mode 100644 index 0000000000..b280f5c6b5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/RpcTarget.java @@ -0,0 +1,28 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.Serializable; + +import com.vaadin.terminal.VariableOwner; + +/** + * Marker interface for server side classes that can receive RPC calls. + * + * This plays a role similar to that of {@link VariableOwner}. + * + * @since 7.0 + */ +public interface RpcTarget extends Serializable { + /** + * Returns the RPC manager instance to use when receiving calls for an RPC + * interface. + * + * @param rpcInterface + * interface for which the call was made + * @return RpcManager or null if none found for the interface + */ + public RpcManager getRpcManager(Class<?> rpcInterface); +} diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java new file mode 100644 index 0000000000..07f83864c2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java @@ -0,0 +1,138 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.vaadin.terminal.gwt.client.Connector; + +/** + * Server side RPC manager that handles RPC calls coming from the client. + * + * Each {@link RpcTarget} (typically a {@link ClientConnector}) should have its + * own instance of {@link ServerRpcManager} if it wants to receive RPC calls + * from the client. + * + * @since 7.0 + */ +public class ServerRpcManager<T> implements RpcManager { + + private final T implementation; + private final Class<T> rpcInterface; + + private static final Map<Class<?>, Class<?>> boxedTypes = new HashMap<Class<?>, Class<?>>(); + static { + try { + Class<?>[] boxClasses = new Class<?>[] { Boolean.class, Byte.class, + Short.class, Character.class, Integer.class, Long.class, + Float.class, Double.class }; + for (Class<?> boxClass : boxClasses) { + Field typeField = boxClass.getField("TYPE"); + Class<?> primitiveType = (Class<?>) typeField.get(boxClass); + boxedTypes.put(primitiveType, boxClass); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Create a RPC manager for an RPC target. + * + * @param target + * RPC call target (normally a {@link Connector}) + * @param implementation + * RPC interface implementation for the target + * @param rpcInterface + * RPC interface type + */ + public ServerRpcManager(T implementation, Class<T> rpcInterface) { + this.implementation = implementation; + this.rpcInterface = rpcInterface; + } + + /** + * Invoke a method in a server side RPC target class. This method is to be + * used by the RPC framework and unit testing tools only. + * + * @param target + * non-null target of the RPC call + * @param invocation + * method invocation to perform + */ + public static void applyInvocation(RpcTarget target, + ServerRpcMethodInvocation invocation) { + RpcManager manager = target.getRpcManager(invocation + .getInterfaceClass()); + if (manager != null) { + manager.applyInvocation(invocation); + } else { + getLogger() + .log(Level.WARNING, + "RPC call received for RpcTarget " + + target.getClass().getName() + + " (" + + invocation.getConnectorId() + + ") but the target has not registered any RPC interfaces"); + } + } + + /** + * Returns the RPC interface implementation for the RPC target. + * + * @return RPC interface implementation + */ + protected T getImplementation() { + return implementation; + } + + /** + * Returns the RPC interface type managed by this RPC manager instance. + * + * @return RPC interface type + */ + protected Class<T> getRpcInterface() { + return rpcInterface; + } + + /** + * Invoke a method in a server side RPC target class. This method is to be + * used by the RPC framework and unit testing tools only. + * + * @param invocation + * method invocation to perform + */ + public void applyInvocation(ServerRpcMethodInvocation invocation) { + Method method = invocation.getMethod(); + Class<?>[] parameterTypes = method.getParameterTypes(); + Object[] args = new Object[parameterTypes.length]; + Object[] arguments = invocation.getParameters(); + for (int i = 0; i < args.length; i++) { + // no conversion needed for basic cases + // Class<?> type = parameterTypes[i]; + // if (type.isPrimitive()) { + // type = boxedTypes.get(type); + // } + args[i] = arguments[i]; + } + try { + method.invoke(implementation, args); + } catch (Exception e) { + throw new RuntimeException("Unable to invoke method " + + invocation.getMethodName() + " in " + + invocation.getInterfaceName(), e); + } + } + + private static Logger getLogger() { + return Logger.getLogger(ServerRpcManager.class.getName()); + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java new file mode 100644 index 0000000000..6f278f7797 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java @@ -0,0 +1,107 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.server; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public class ServerRpcMethodInvocation extends MethodInvocation { + + private static final Map<String, Method> invocationMethodCache = new ConcurrentHashMap<String, Method>( + 128, 0.75f, 1); + + private final Method method; + + private Class<? extends ServerRpc> interfaceClass; + + public ServerRpcMethodInvocation(String connectorId, String interfaceName, + String methodName, int parameterCount) { + super(connectorId, interfaceName, methodName); + + interfaceClass = findClass(); + method = findInvocationMethod(interfaceClass, methodName, + parameterCount); + } + + private Class<? extends ServerRpc> findClass() { + try { + Class<?> rpcInterface = Class.forName(getInterfaceName()); + if (!ServerRpc.class.isAssignableFrom(rpcInterface)) { + throw new IllegalArgumentException("The interface " + + getInterfaceName() + "is not a server RPC interface."); + } + return (Class<? extends ServerRpc>) rpcInterface; + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("The server RPC interface " + + getInterfaceName() + " could not be found", e); + } finally { + + } + } + + public Class<? extends ServerRpc> getInterfaceClass() { + return interfaceClass; + } + + public Method getMethod() { + return method; + } + + /** + * Tries to find the method from the cache or alternatively by invoking + * {@link #doFindInvocationMethod(Class, String, int)} and updating the + * cache. + * + * @param targetType + * @param methodName + * @param parameterCount + * @return + */ + private Method findInvocationMethod(Class<?> targetType, String methodName, + int parameterCount) { + // TODO currently only using method name and number of parameters as the + // signature + String signature = targetType.getName() + "." + methodName + "(" + + parameterCount; + Method invocationMethod = invocationMethodCache.get(signature); + + if (invocationMethod == null) { + invocationMethod = doFindInvocationMethod(targetType, methodName, + parameterCount); + + if (invocationMethod != null) { + invocationMethodCache.put(signature, invocationMethod); + } + } + + return invocationMethod; + } + + /** + * Tries to find the method from the class by looping through available + * methods. + * + * @param targetType + * @param methodName + * @param parameterCount + * @return + */ + private Method doFindInvocationMethod(Class<?> targetType, + String methodName, int parameterCount) { + Method[] methods = targetType.getMethods(); + for (Method method : methods) { + Class<?>[] parameterTypes = method.getParameterTypes(); + if (method.getName().equals(methodName) + && parameterTypes.length == parameterCount) { + return method; + } + } + return null; + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java b/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java new file mode 100644 index 0000000000..9b1e60e621 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java @@ -0,0 +1,73 @@ +package com.vaadin.terminal.gwt.server; + +import java.io.Serializable; + +import com.vaadin.Application; +import com.vaadin.ui.Root; + +/* + @VaadinApache2LicenseForJavaFiles@ + */ + +class ServletPortletHelper implements Serializable { + public static class ApplicationClassException extends Exception { + + public ApplicationClassException(String message, Throwable cause) { + super(message, cause); + } + + public ApplicationClassException(String message) { + super(message); + } + } + + static Class<? extends Application> getApplicationClass( + String applicationParameter, String rootParameter, + ClassLoader classLoader) throws ApplicationClassException { + if (applicationParameter == null) { + + // Validate the parameter value + verifyRootClass(rootParameter, classLoader); + + // Application can be used if a valid rootLayout is defined + return Application.class; + } + + try { + return (Class<? extends Application>) classLoader + .loadClass(applicationParameter); + } catch (final ClassNotFoundException e) { + throw new ApplicationClassException( + "Failed to load application class: " + applicationParameter, + e); + } + } + + private static void verifyRootClass(String className, + ClassLoader classLoader) throws ApplicationClassException { + if (className == null) { + throw new ApplicationClassException(Application.ROOT_PARAMETER + + " init parameter not defined"); + } + + // Check that the root layout class can be found + try { + Class<?> rootClass = classLoader.loadClass(className); + if (!Root.class.isAssignableFrom(rootClass)) { + throw new ApplicationClassException(className + + " does not implement Root"); + } + // Try finding a default constructor, else throw exception + rootClass.getConstructor(); + } catch (ClassNotFoundException e) { + throw new ApplicationClassException(className + + " could not be loaded", e); + } catch (SecurityException e) { + throw new ApplicationClassException("Could not access " + className + + " class", e); + } catch (NoSuchMethodException e) { + throw new ApplicationClassException(className + + " doesn't have a public no-args constructor"); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/server/UnsupportedBrowserHandler.java b/src/com/vaadin/terminal/gwt/server/UnsupportedBrowserHandler.java new file mode 100644 index 0000000000..334a7acf8d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/UnsupportedBrowserHandler.java @@ -0,0 +1,88 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.server; + +import java.io.IOException; +import java.io.Writer; + +import com.vaadin.Application; +import com.vaadin.terminal.RequestHandler; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; + +/** + * A {@link RequestHandler} that presents an informative page if the browser in + * use is unsupported. Recognizes Chrome Frame and allow it to be used. + * + * <p> + * This handler is usually added to the application by + * {@link AbstractCommunicationManager}. + * </p> + */ +@SuppressWarnings("serial") +public class UnsupportedBrowserHandler implements RequestHandler { + + /** Cookie used to ignore browser checks */ + public static final String FORCE_LOAD_COOKIE = "vaadinforceload=1"; + + public boolean handleRequest(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException { + + if (request.getBrowserDetails() != null) { + // Check if the browser is supported + // If Chrome Frame is available we'll assume it's ok + WebBrowser b = request.getBrowserDetails().getWebBrowser(); + if (b.isTooOldToFunctionProperly() && !b.isChromeFrameCapable()) { + // bypass if cookie set + String c = request.getHeader("Cookie"); + if (c == null || !c.contains(FORCE_LOAD_COOKIE)) { + writeBrowserTooOldPage(request, response); + return true; // request handled + } + } + } + + return false; // pass to next handler + } + + /** + * Writes a page encouraging the user to upgrade to a more current browser. + * + * @param request + * @param response + * @throws IOException + */ + protected void writeBrowserTooOldPage(WrappedRequest request, + WrappedResponse response) throws IOException { + Writer page = response.getWriter(); + WebBrowser b = request.getBrowserDetails().getWebBrowser(); + + page.write("<html><body><h1>I'm sorry, but your browser is not supported</h1>" + + "<p>The version (" + + b.getBrowserMajorVersion() + + "." + + b.getBrowserMinorVersion() + + ") of the browser you are using " + + " is outdated and not supported.</p>" + + "<p>You should <b>consider upgrading</b> to a more up-to-date browser.</p> " + + "<p>The most popular browsers are <b>" + + " <a href=\"https://www.google.com/chrome\">Chrome</a>," + + " <a href=\"http://www.mozilla.com/firefox\">Firefox</a>," + + (b.isWindows() ? " <a href=\"http://windows.microsoft.com/en-US/internet-explorer/downloads/ie\">Internet Explorer</a>," + : "") + + " <a href=\"http://www.opera.com/browser\">Opera</a>" + + " and <a href=\"http://www.apple.com/safari\">Safari</a>.</b><br/>" + + "Upgrading to the latest version of one of these <b>will make the web safer, faster and better looking.</b></p>" + + (b.isIE() ? "<script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js\"></script>" + + "<p>If you can not upgrade your browser, please consider trying <a onclick=\"CFInstall.check({mode:'overlay'});return false;\" href=\"http://www.google.com/chromeframe\">Chrome Frame</a>.</p>" + : "") // + + "<p><sub><a onclick=\"document.cookie='" + + FORCE_LOAD_COOKIE + + "';window.location.reload();return false;\" href=\"#\">Continue without updating</a> (not recommended)</sub></p>" + + "</body>\n" + "</html>"); + + page.close(); + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/WebBrowser.java b/src/com/vaadin/terminal/gwt/server/WebBrowser.java index a57a4c65d2..358f6f38fb 100644 --- a/src/com/vaadin/terminal/gwt/server/WebBrowser.java +++ b/src/com/vaadin/terminal/gwt/server/WebBrowser.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.Locale; import com.vaadin.terminal.Terminal; +import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.gwt.client.VBrowserDetails; /** @@ -189,6 +190,34 @@ public class WebBrowser implements Terminal { } /** + * Tests whether the user is using Chrome Frame. + * + * @return true if the user is using Chrome Frame, false if the user is not + * using Chrome or if no information on the browser is present + */ + public boolean isChromeFrame() { + if (browserDetails == null) { + return false; + } + + return browserDetails.isChromeFrame(); + } + + /** + * Tests whether the user's browser is Chrome Frame capable. + * + * @return true if the user can use Chrome Frame, false if the user can not + * or if no information on the browser is present + */ + public boolean isChromeFrameCapable() { + if (browserDetails == null) { + return false; + } + + return browserDetails.isChromeFrameCapable(); + } + + /** * Gets the major version of the browser the user is using. * * <p> @@ -417,25 +446,50 @@ public class WebBrowser implements Terminal { * only. Updates all properties in the class according to the given * information. * - * @param locale - * The browser primary locale - * @param address - * The browser ip address - * @param secureConnection - * true if using an https connection - * @param agent - * Raw userAgent string from the browser + * @param request + * the wrapped request to read the information from */ - void updateRequestDetails(Locale locale, String address, - boolean secureConnection, String agent) { - this.locale = locale; - this.address = address; - this.secureConnection = secureConnection; + void updateRequestDetails(WrappedRequest request) { + locale = request.getLocale(); + address = request.getRemoteAddr(); + secureConnection = request.isSecure(); + String agent = request.getHeader("user-agent"); + if (agent != null) { browserApplication = agent; browserDetails = new VBrowserDetails(agent); } + if (request.getParameter("sw") != null) { + updateClientSideDetails(request.getParameter("sw"), + request.getParameter("sh"), request.getParameter("cw"), + request.getParameter("ch"), request.getParameter("tzo"), + request.getParameter("rtzo"), request.getParameter("dstd"), + request.getParameter("dston"), + request.getParameter("curdate"), + request.getParameter("td") != null); + } + } + + /** + * Checks if the browser is so old that it simply won't work with a Vaadin + * application. Can be used to redirect to an alternative page, show + * alternative content or similar. + * + * When this method returns true chances are very high that the browser + * won't work and it does not make sense to direct the user to the Vaadin + * application. + * + * @return true if the browser won't work, false if not the browser is + * supported or might work + */ + public boolean isTooOldToFunctionProperly() { + if (browserDetails == null) { + // Don't know, so assume it will work + return false; + } + + return browserDetails.isTooOldToFunctionProperly(); } } diff --git a/src/com/vaadin/terminal/gwt/server/WrappedHttpServletRequest.java b/src/com/vaadin/terminal/gwt/server/WrappedHttpServletRequest.java new file mode 100644 index 0000000000..b6f1a192cb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/WrappedHttpServletRequest.java @@ -0,0 +1,109 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import com.vaadin.Application; +import com.vaadin.terminal.CombinedRequest; +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.WrappedRequest; + +/** + * Wrapper for {@link HttpServletRequest}. + * + * @author Vaadin Ltd. + * @since 7.0 + * + * @see WrappedRequest + * @see WrappedHttpServletResponse + */ +public class WrappedHttpServletRequest extends HttpServletRequestWrapper + implements WrappedRequest { + + private final DeploymentConfiguration deploymentConfiguration; + + /** + * Wraps a http servlet request and associates with a deployment + * configuration + * + * @param request + * the http servlet request to wrap + * @param deploymentConfiguration + * the associated deployment configuration + */ + public WrappedHttpServletRequest(HttpServletRequest request, + DeploymentConfiguration deploymentConfiguration) { + super(request); + this.deploymentConfiguration = deploymentConfiguration; + } + + public String getRequestPathInfo() { + return getPathInfo(); + } + + public int getSessionMaxInactiveInterval() { + return getSession().getMaxInactiveInterval(); + } + + public Object getSessionAttribute(String name) { + return getSession().getAttribute(name); + } + + public void setSessionAttribute(String name, Object attribute) { + getSession().setAttribute(name, attribute); + } + + /** + * Gets the original, unwrapped HTTP servlet request. + * + * @return the servlet request + */ + public HttpServletRequest getHttpServletRequest() { + return this; + } + + public DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + + public BrowserDetails getBrowserDetails() { + return new BrowserDetails() { + public String getUriFragment() { + return null; + } + + public String getWindowName() { + return null; + } + + public WebBrowser getWebBrowser() { + WebApplicationContext context = (WebApplicationContext) Application + .getCurrentApplication().getContext(); + return context.getBrowser(); + } + }; + } + + /** + * Helper method to get a <code>WrappedHttpServletRequest</code> from a + * <code>WrappedRequest</code>. Aside from casting, this method also takes + * care of situations where there's another level of wrapping. + * + * @param request + * a wrapped request + * @return a wrapped http servlet request + * @throws ClassCastException + * if the wrapped request doesn't wrap a http servlet request + */ + public static WrappedHttpServletRequest cast(WrappedRequest request) { + if (request instanceof CombinedRequest) { + CombinedRequest combinedRequest = (CombinedRequest) request; + request = combinedRequest.getSecondRequest(); + } + return (WrappedHttpServletRequest) request; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/WrappedHttpServletResponse.java b/src/com/vaadin/terminal/gwt/server/WrappedHttpServletResponse.java new file mode 100644 index 0000000000..14a391b21f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/WrappedHttpServletResponse.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.WrappedResponse; + +/** + * Wrapper for {@link HttpServletResponse}. + * + * @author Vaadin Ltd. + * @since 7.0 + * + * @see WrappedResponse + * @see WrappedHttpServletRequest + */ +public class WrappedHttpServletResponse extends HttpServletResponseWrapper + implements WrappedResponse { + + private DeploymentConfiguration deploymentConfiguration; + + /** + * Wraps a http servlet response and an associated deployment configuration + * + * @param response + * the http servlet response to wrap + * @param deploymentConfiguration + * the associated deployment configuration + */ + public WrappedHttpServletResponse(HttpServletResponse response, + DeploymentConfiguration deploymentConfiguration) { + super(response); + this.deploymentConfiguration = deploymentConfiguration; + } + + /** + * Gets the original unwrapped <code>HttpServletResponse</code> + * + * @return the unwrapped response + */ + public HttpServletResponse getHttpServletResponse() { + return this; + } + + public void setCacheTime(long milliseconds) { + doSetCacheTime(this, milliseconds); + } + + // Implementation shared with WrappedPortletResponse + static void doSetCacheTime(WrappedResponse response, long milliseconds) { + if (milliseconds <= 0) { + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + } else { + response.setHeader("Cache-Control", "max-age=" + milliseconds + / 1000); + response.setDateHeader("Expires", System.currentTimeMillis() + + milliseconds); + // Required to apply caching in some Tomcats + response.setHeader("Pragma", "cache"); + } + } + + public DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java b/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java new file mode 100644 index 0000000000..3838695aa3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java @@ -0,0 +1,189 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.Map; + +import javax.portlet.ClientDataRequest; +import javax.portlet.PortletRequest; +import javax.portlet.ResourceRequest; + +import com.vaadin.Application; +import com.vaadin.terminal.CombinedRequest; +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.WrappedRequest; + +/** + * Wrapper for {@link PortletRequest} and its subclasses. + * + * @author Vaadin Ltd. + * @since 7.0 + * + * @see WrappedRequest + * @see WrappedPortletResponse + */ +public class WrappedPortletRequest implements WrappedRequest { + + private final PortletRequest request; + private final DeploymentConfiguration deploymentConfiguration; + + /** + * Wraps a portlet request and an associated deployment configuration + * + * @param request + * the portlet request to wrap + * @param deploymentConfiguration + * the associated deployment configuration + */ + public WrappedPortletRequest(PortletRequest request, + DeploymentConfiguration deploymentConfiguration) { + this.request = request; + this.deploymentConfiguration = deploymentConfiguration; + } + + public Object getAttribute(String name) { + return request.getAttribute(name); + } + + public int getContentLength() { + try { + return ((ClientDataRequest) request).getContentLength(); + } catch (ClassCastException e) { + throw new IllegalStateException( + "Content lenght only available for ClientDataRequests"); + } + } + + public InputStream getInputStream() throws IOException { + try { + return ((ClientDataRequest) request).getPortletInputStream(); + } catch (ClassCastException e) { + throw new IllegalStateException( + "Input data only available for ClientDataRequests"); + } + } + + public String getParameter(String name) { + return request.getParameter(name); + } + + public Map<String, String[]> getParameterMap() { + return request.getParameterMap(); + } + + public void setAttribute(String name, Object o) { + request.setAttribute(name, o); + } + + public String getRequestPathInfo() { + if (request instanceof ResourceRequest) { + return ((ResourceRequest) request).getResourceID(); + } else { + return null; + } + } + + public int getSessionMaxInactiveInterval() { + return request.getPortletSession().getMaxInactiveInterval(); + } + + public Object getSessionAttribute(String name) { + return request.getPortletSession().getAttribute(name); + } + + public void setSessionAttribute(String name, Object attribute) { + request.getPortletSession().setAttribute(name, attribute); + } + + /** + * Gets the original, unwrapped portlet request. + * + * @return the unwrapped portlet request + */ + public PortletRequest getPortletRequest() { + return request; + } + + public String getContentType() { + try { + return ((ResourceRequest) request).getContentType(); + } catch (ClassCastException e) { + throw new IllegalStateException( + "Content type only available for ResourceRequests"); + } + } + + public BrowserDetails getBrowserDetails() { + return new BrowserDetails() { + public String getUriFragment() { + return null; + } + + public String getWindowName() { + return null; + } + + public WebBrowser getWebBrowser() { + PortletApplicationContext2 context = (PortletApplicationContext2) Application + .getCurrentApplication().getContext(); + return context.getBrowser(); + } + }; + } + + public Locale getLocale() { + return request.getLocale(); + } + + public String getRemoteAddr() { + return null; + } + + public boolean isSecure() { + return request.isSecure(); + } + + public String getHeader(String string) { + return null; + } + + /** + * Reads a portal property from the portal context of the wrapped request. + * + * @param name + * a string with the name of the portal property to get + * @return a string with the value of the property, or <code>null</code> if + * the property is not defined + */ + public String getPortalProperty(String name) { + return request.getPortalContext().getProperty(name); + } + + public DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + + /** + * Helper method to get a <code>WrappedPortlettRequest</code> from a + * <code>WrappedRequest</code>. Aside from casting, this method also takes + * care of situations where there's another level of wrapping. + * + * @param request + * a wrapped request + * @return a wrapped portlet request + * @throws ClassCastException + * if the wrapped request doesn't wrap a portlet request + */ + public static WrappedPortletRequest cast(WrappedRequest request) { + if (request instanceof CombinedRequest) { + CombinedRequest combinedRequest = (CombinedRequest) request; + request = combinedRequest.getSecondRequest(); + } + return (WrappedPortletRequest) request; + } +} diff --git a/src/com/vaadin/terminal/gwt/server/WrappedPortletResponse.java b/src/com/vaadin/terminal/gwt/server/WrappedPortletResponse.java new file mode 100644 index 0000000000..8824396352 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/WrappedPortletResponse.java @@ -0,0 +1,102 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import javax.portlet.MimeResponse; +import javax.portlet.PortletResponse; +import javax.portlet.ResourceResponse; + +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.WrappedResponse; + +/** + * Wrapper for {@link PortletResponse} and its subclasses. + * + * @author Vaadin Ltd. + * @since 7.0 + * + * @see WrappedResponse + * @see WrappedPortletRequest + */ +public class WrappedPortletResponse implements WrappedResponse { + private static final DateFormat HTTP_DATE_FORMAT = new SimpleDateFormat( + "EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH); + static { + HTTP_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT")); + } + + private final PortletResponse response; + private DeploymentConfiguration deploymentConfiguration; + + /** + * Wraps a portlet response and an associated deployment configuration + * + * @param response + * the portlet response to wrap + * @param deploymentConfiguration + * the associated deployment configuration + */ + public WrappedPortletResponse(PortletResponse response, + DeploymentConfiguration deploymentConfiguration) { + this.response = response; + this.deploymentConfiguration = deploymentConfiguration; + } + + public OutputStream getOutputStream() throws IOException { + return ((MimeResponse) response).getPortletOutputStream(); + } + + /** + * Gets the original, unwrapped portlet response. + * + * @return the unwrapped portlet response + */ + public PortletResponse getPortletResponse() { + return response; + } + + public void setContentType(String type) { + ((MimeResponse) response).setContentType(type); + } + + public PrintWriter getWriter() throws IOException { + return ((MimeResponse) response).getWriter(); + } + + public void setStatus(int responseStatus) { + response.setProperty(ResourceResponse.HTTP_STATUS_CODE, + Integer.toString(responseStatus)); + } + + public void setHeader(String name, String value) { + response.setProperty(name, value); + } + + public void setDateHeader(String name, long timestamp) { + response.setProperty(name, HTTP_DATE_FORMAT.format(new Date(timestamp))); + } + + public void setCacheTime(long milliseconds) { + WrappedHttpServletResponse.doSetCacheTime(this, milliseconds); + } + + public void sendError(int errorCode, String message) throws IOException { + setStatus(errorCode); + getWriter().write(message); + } + + public DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java index 2e4ce39513..6a0aa0f4c2 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java @@ -31,8 +31,7 @@ import java.util.logging.Logger; import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; import com.vaadin.event.dd.acceptcriteria.ClientCriterion; -import com.vaadin.terminal.Paintable; -import com.vaadin.ui.ClientWidget; +import com.vaadin.terminal.gwt.server.ClientConnector; /** * Utility class to collect widgetset related information from classpath. @@ -93,29 +92,27 @@ public class ClassPathExplorer { } /** - * Finds server side widgets with {@link ClientWidget} annotation on the - * class path (entries that can contain widgets/widgetsets - see - * {@link #getRawClasspathEntries()}). + * Finds server side widgets with ClientWidget annotation on the class path + * (entries that can contain widgets/widgetsets - see + * getRawClasspathEntries()). * * As a side effect, also accept criteria are searched under the same class * path entries and added into the acceptCriterion collection. * - * @return a collection of {@link Paintable} classes + * @return a collection of {@link ClientConnector} classes */ - public static Collection<Class<? extends Paintable>> getPaintablesHavingWidgetAnnotation() { - logger.info("Searching for paintables.."); + public static void findAcceptCriteria() { + logger.info("Searching for accept criteria.."); long start = System.currentTimeMillis(); - Collection<Class<? extends Paintable>> paintables = new HashSet<Class<? extends Paintable>>(); Set<String> keySet = classpathLocations.keySet(); for (String url : keySet) { - logger.fine("Searching for paintables in " + logger.fine("Searching for accept criteria in " + classpathLocations.get(url)); - searchForPaintables(classpathLocations.get(url), url, paintables); + searchForPaintables(classpathLocations.get(url), url); } long end = System.currentTimeMillis(); logger.info("Search took " + (end - start) + "ms"); - return paintables; } @@ -129,7 +126,7 @@ public class ClassPathExplorer { if (acceptCriterion.isEmpty()) { // accept criterion are searched as a side effect, normally after // paintable detection - getPaintablesHavingWidgetAnnotation(); + findAcceptCriteria(); } return acceptCriterion; } @@ -449,7 +446,7 @@ public class ClassPathExplorer { /** * Searches for all paintable classes and accept criteria under a location - * based on {@link ClientWidget} and {@link ClientCriterion} annotations. + * based on {@link ClientCriterion} annotations. * * Note that client criteria are updated directly to the * {@link #acceptCriterion} field, whereas paintables are added to the @@ -457,11 +454,9 @@ public class ClassPathExplorer { * * @param location * @param locationString - * @param paintables */ private final static void searchForPaintables(URL location, - String locationString, - Collection<Class<? extends Paintable>> paintables) { + String locationString) { // Get a File object for the package File directory = new File(location.getFile()); @@ -478,7 +473,7 @@ public class ClassPathExplorer { String packageName = locationString .substring(locationString.lastIndexOf("/") + 1); classname = packageName + "." + classname; - tryToAdd(classname, paintables); + tryToAdd(classname); } } } else { @@ -510,7 +505,7 @@ public class ClassPathExplorer { classname = classname.substring(1); } classname = classname.replace('/', '.'); - tryToAdd(classname, paintables); + tryToAdd(classname); } } } @@ -542,17 +537,13 @@ public class ClassPathExplorer { private static Set<Class<? extends AcceptCriterion>> acceptCriterion = new HashSet<Class<? extends AcceptCriterion>>(); /** - * Checks a class for the {@link ClientWidget} and {@link ClientCriterion} - * annotations, and adds it to the appropriate collection if it has either. + * Checks a class for the {@link ClientCriterion} annotations, and adds it + * to the appropriate collection. * * @param fullclassName - * @param paintables - * the collection to which to add server side classes with - * {@link ClientWidget} annotation */ @SuppressWarnings("unchecked") - private static void tryToAdd(final String fullclassName, - Collection<Class<? extends Paintable>> paintables) { + private static void tryToAdd(final String fullclassName) { PrintStream out = System.out; PrintStream err = System.err; Throwable errorToShow = null; @@ -563,10 +554,7 @@ public class ClassPathExplorer { Class<?> c = Class.forName(fullclassName); - if (c.getAnnotation(ClientWidget.class) != null) { - paintables.add((Class<? extends Paintable>) c); - // System.out.println("Found paintable " + fullclassName); - } else if (c.getAnnotation(ClientCriterion.class) != null) { + if (c.getAnnotation(ClientCriterion.class) != null) { acceptCriterion.add((Class<? extends AcceptCriterion>) c); } } catch (UnsupportedClassVersionError e) { @@ -667,10 +655,9 @@ public class ClassPathExplorer { * Test method for helper tool */ public static void main(String[] args) { - Collection<Class<? extends Paintable>> paintables = ClassPathExplorer - .getPaintablesHavingWidgetAnnotation(); - logger.info("Found annotated paintables:"); - for (Class<? extends Paintable> cls : paintables) { + ClassPathExplorer.findAcceptCriteria(); + logger.info("Found client criteria:"); + for (Class<? extends AcceptCriterion> cls : acceptCriterion) { logger.info(cls.getCanonicalName()); } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java index 4ea4cbb8fe..f0d6f0453b 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java @@ -6,57 +6,58 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.util.Collection; import java.util.HashSet; -import com.vaadin.terminal.Paintable; -import com.vaadin.ui.ClientWidget; -import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; /** * An abstract helper class that can be used to easily build a widgetset with * customized load styles for each components. In three abstract methods one can - * override the default values given in {@link ClientWidget} annotations. + * override the default values given in {@link Connect} annotations. * * @see WidgetMapGenerator * */ public abstract class CustomWidgetMapGenerator extends WidgetMapGenerator { - private Collection<Class<? extends Paintable>> eagerPaintables = new HashSet<Class<? extends Paintable>>(); - private Collection<Class<? extends Paintable>> lazyPaintables = new HashSet<Class<? extends Paintable>>(); - private Collection<Class<? extends Paintable>> deferredPaintables = new HashSet<Class<? extends Paintable>>(); + private Collection<Class<? extends ComponentConnector>> eagerPaintables = new HashSet<Class<? extends ComponentConnector>>(); + private Collection<Class<? extends ComponentConnector>> lazyPaintables = new HashSet<Class<? extends ComponentConnector>>(); + private Collection<Class<? extends ComponentConnector>> deferredPaintables = new HashSet<Class<? extends ComponentConnector>>(); @Override - protected LoadStyle getLoadStyle(Class<? extends Paintable> paintableType) { + protected LoadStyle getLoadStyle( + Class<? extends ComponentConnector> connector) { if (eagerPaintables == null) { init(); } - if (eagerPaintables.contains(paintableType)) { + if (eagerPaintables.contains(connector)) { return LoadStyle.EAGER; } - if (lazyPaintables.contains(paintableType)) { + if (lazyPaintables.contains(connector)) { return LoadStyle.LAZY; } - if (deferredPaintables.contains(paintableType)) { + if (deferredPaintables.contains(connector)) { return LoadStyle.DEFERRED; } - return super.getLoadStyle(paintableType); + return super.getLoadStyle(connector); } private void init() { - Class<? extends Paintable>[] eagerComponents = getEagerComponents(); + Class<? extends ComponentConnector>[] eagerComponents = getEagerComponents(); if (eagerComponents != null) { - for (Class<? extends Paintable> class1 : eagerComponents) { + for (Class<? extends ComponentConnector> class1 : eagerComponents) { eagerPaintables.add(class1); } } - Class<? extends Paintable>[] lazyComponents = getEagerComponents(); + Class<? extends ComponentConnector>[] lazyComponents = getEagerComponents(); if (lazyComponents != null) { - for (Class<? extends Paintable> class1 : lazyComponents) { + for (Class<? extends ComponentConnector> class1 : lazyComponents) { lazyPaintables.add(class1); } } - Class<? extends Paintable>[] deferredComponents = getEagerComponents(); + Class<? extends ComponentConnector>[] deferredComponents = getEagerComponents(); if (deferredComponents != null) { - for (Class<? extends Paintable> class1 : deferredComponents) { + for (Class<? extends ComponentConnector> class1 : deferredComponents) { deferredPaintables.add(class1); } } @@ -66,18 +67,18 @@ public abstract class CustomWidgetMapGenerator extends WidgetMapGenerator { * @return an array of components whose load style should be overridden to * {@link LoadStyle#EAGER} */ - protected abstract Class<? extends Paintable>[] getEagerComponents(); + protected abstract Class<? extends ComponentConnector>[] getEagerComponents(); /** * @return an array of components whose load style should be overridden to * {@link LoadStyle#LAZY} */ - protected abstract Class<? extends Paintable>[] getLazyComponents(); + protected abstract Class<? extends ComponentConnector>[] getLazyComponents(); /** * @return an array of components whose load style should be overridden to * {@link LoadStyle#DEFERRED} */ - protected abstract Class<? extends Paintable>[] getDeferredComponents(); + protected abstract Class<? extends ComponentConnector>[] getDeferredComponents(); } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java index 6381a3b4cb..8a1dfee3b5 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java @@ -3,8 +3,8 @@ */ package com.vaadin.terminal.gwt.widgetsetutils; -import com.vaadin.terminal.Paintable; -import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; /** * WidgetMap generator that builds a widgetset that packs all included widgets @@ -21,8 +21,10 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * */ public class EagerWidgetMapGenerator extends WidgetMapGenerator { + @Override - protected LoadStyle getLoadStyle(Class<? extends Paintable> paintableType) { + protected LoadStyle getLoadStyle( + Class<? extends ComponentConnector> connector) { return LoadStyle.EAGER; } } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java index 7de72f09ce..729a999a21 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java @@ -3,8 +3,8 @@ */ package com.vaadin.terminal.gwt.widgetsetutils; -import com.vaadin.terminal.Paintable; -import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; /** * WidgetMap generator that builds a widgetset that optimizes the transferred @@ -16,7 +16,8 @@ import com.vaadin.ui.ClientWidget.LoadStyle; */ public class LazyWidgetMapGenerator extends WidgetMapGenerator { @Override - protected LoadStyle getLoadStyle(Class<? extends Paintable> paintableType) { + protected LoadStyle getLoadStyle( + Class<? extends ComponentConnector> connector) { return LoadStyle.LAZY; } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java new file mode 100644 index 0000000000..2899061204 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java @@ -0,0 +1,197 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.widgetsetutils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.RpcManager; + +/** + * GWT generator that creates an implementation for {@link RpcManager} on the + * client side classes for executing RPC calls received from the the server. + * + * @since 7.0 + */ +public class RpcManagerGenerator extends Generator { + + @Override + public String generate(TreeLogger logger, GeneratorContext context, + String typeName) throws UnableToCompleteException { + + String packageName = null; + String className = null; + try { + TypeOracle typeOracle = context.getTypeOracle(); + + // get classType and save instance variables + JClassType classType = typeOracle.getType(typeName); + packageName = classType.getPackage().getName(); + className = classType.getSimpleSourceName() + "Impl"; + // Generate class source code for SerializerMapImpl + generateClass(logger, context, packageName, className); + } catch (Exception e) { + logger.log(TreeLogger.ERROR, + "SerializerMapGenerator creation failed", e); + } + // return the fully qualifed name of the class generated + return packageName + "." + className; + } + + /** + * Generate source code for RpcManagerImpl + * + * @param logger + * Logger object + * @param context + * Generator context + * @param packageName + * package name for the class to generate + * @param className + * class name for the class to generate + */ + private void generateClass(TreeLogger logger, GeneratorContext context, + String packageName, String className) { + // get print writer that receives the source code + PrintWriter printWriter = null; + printWriter = context.tryCreate(logger, packageName, className); + // print writer if null, source code has ALREADY been generated + if (printWriter == null) { + return; + } + logger.log(Type.INFO, + "Detecting server to client RPC interface types..."); + Date date = new Date(); + TypeOracle typeOracle = context.getTypeOracle(); + JClassType serverToClientRpcType = typeOracle.findType(ClientRpc.class + .getName()); + JClassType[] rpcInterfaceSubtypes = serverToClientRpcType.getSubtypes(); + + // init composer, set class properties, create source writer + ClassSourceFileComposerFactory composer = null; + composer = new ClassSourceFileComposerFactory(packageName, className); + composer.addImport("com.google.gwt.core.client.GWT"); + composer.addImplementedInterface(RpcManager.class.getName()); + SourceWriter sourceWriter = composer.createSourceWriter(context, + printWriter); + sourceWriter.indent(); + + List<JClassType> rpcInterfaces = new ArrayList<JClassType>(); + + // iterate over RPC interfaces and create helper methods for each + // interface + for (JClassType type : rpcInterfaceSubtypes) { + if (null == type.isInterface()) { + // only interested in interfaces here, not implementations + continue; + } + rpcInterfaces.add(type); + // generate method to call methods of an RPC interface + sourceWriter.println("private void " + getInvokeMethodName(type) + + "(" + MethodInvocation.class.getName() + " invocation, " + + ConnectorMap.class.getName() + " connectorMap) {"); + sourceWriter.indent(); + + // loop over the methods of the interface and its superinterfaces + // methods + for (JClassType currentType : type.getFlattenedSupertypeHierarchy()) { + for (JMethod method : currentType.getMethods()) { + sourceWriter.println("if (\"" + method.getName() + + "\".equals(invocation.getMethodName())) {"); + sourceWriter.indent(); + // construct parameter string with appropriate casts + String paramString = ""; + JType[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; ++i) { + paramString = paramString + "(" + + parameterTypes[i].getQualifiedSourceName() + + ") invocation.getParameters()[" + i + "]"; + if (i < parameterTypes.length - 1) { + paramString = paramString + ", "; + } + } + sourceWriter + .println(ServerConnector.class.getName() + + " connector = connectorMap.getConnector(invocation.getConnectorId());"); + sourceWriter + .println("for (" + + ClientRpc.class.getName() + + " rpcImplementation : connector.getRpcImplementations(\"" + + type.getQualifiedSourceName() + "\")) {"); + sourceWriter.indent(); + sourceWriter.println("((" + type.getQualifiedSourceName() + + ") rpcImplementation)." + method.getName() + "(" + + paramString + ");"); + sourceWriter.outdent(); + sourceWriter.println("}"); + sourceWriter.println("return;"); + sourceWriter.outdent(); + sourceWriter.println("}"); + } + } + + sourceWriter.outdent(); + sourceWriter.println("}"); + + logger.log(Type.DEBUG, + "Constructed helper method for server to client RPC for " + + type.getName()); + } + + // generate top-level "switch-case" method to select the correct + // previously generated method based on the RPC interface + sourceWriter.println("public void applyInvocation(" + + MethodInvocation.class.getName() + " invocation, " + + ConnectorMap.class.getName() + " connectorMap) {"); + sourceWriter.indent(); + + for (JClassType type : rpcInterfaces) { + sourceWriter.println("if (\"" + type.getQualifiedSourceName() + + "\".equals(invocation.getInterfaceName())) {"); + sourceWriter.indent(); + sourceWriter.println(getInvokeMethodName(type) + + "(invocation, connectorMap);"); + sourceWriter.println("return;"); + sourceWriter.outdent(); + sourceWriter.println("}"); + + logger.log(Type.INFO, + "Configured server to client RPC for " + type.getName()); + } + sourceWriter.outdent(); + sourceWriter.println("}"); + + // close generated class + sourceWriter.outdent(); + sourceWriter.println("}"); + // commit generated class + context.commit(logger, printWriter); + logger.log(Type.INFO, + "Done. (" + (new Date().getTime() - date.getTime()) / 1000 + + "seconds)"); + + } + + private String getInvokeMethodName(JClassType type) { + return "invoke" + type.getQualifiedSourceName().replaceAll("\\.", "_"); + } +} diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyCreatorGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyCreatorGenerator.java new file mode 100644 index 0000000000..040715fccf --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyCreatorGenerator.java @@ -0,0 +1,126 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.widgetsetutils; + +import java.io.PrintWriter; +import java.util.Date; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.communication.InitializableServerRpc; +import com.vaadin.terminal.gwt.client.communication.RpcProxy.RpcProxyCreator; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public class RpcProxyCreatorGenerator extends Generator { + + @Override + public String generate(TreeLogger logger, GeneratorContext ctx, + String requestedClassName) throws UnableToCompleteException { + logger.log(TreeLogger.DEBUG, "Running RpcProxyCreatorGenerator"); + TypeOracle typeOracle = ctx.getTypeOracle(); + assert (typeOracle != null); + + JClassType requestedType = typeOracle.findType(requestedClassName); + String packageName = requestedType.getPackage().getName(); + String className = requestedType.getSimpleSourceName() + "Impl"; + if (requestedType == null) { + logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + + requestedClassName + "'", null); + throw new UnableToCompleteException(); + } + + createType(logger, ctx, packageName, className); + return packageName + "." + className; + } + + private void createType(TreeLogger logger, GeneratorContext context, + String packageName, String className) { + ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory( + packageName, className); + + PrintWriter printWriter = context.tryCreate(logger, + composer.getCreatedPackage(), + composer.getCreatedClassShortName()); + if (printWriter == null) { + // print writer is null if source code has already been generated + return; + } + Date date = new Date(); + TypeOracle typeOracle = context.getTypeOracle(); + + // init composer, set class properties, create source writer + composer.addImport(GWT.class.getCanonicalName()); + composer.addImport(ServerRpc.class.getCanonicalName()); + composer.addImport(ServerConnector.class.getCanonicalName()); + composer.addImport(InitializableServerRpc.class.getCanonicalName()); + composer.addImport(IllegalArgumentException.class.getCanonicalName()); + composer.addImplementedInterface(RpcProxyCreator.class + .getCanonicalName()); + + SourceWriter sourceWriter = composer.createSourceWriter(context, + printWriter); + sourceWriter.indent(); + + sourceWriter + .println("public <T extends ServerRpc> T create(Class<T> rpcInterface, ServerConnector connector) {"); + sourceWriter.indent(); + + sourceWriter + .println("if (rpcInterface == null || connector == null) {"); + sourceWriter.indent(); + sourceWriter + .println("throw new IllegalArgumentException(\"RpcInterface and/or connector cannot be null\");"); + sourceWriter.outdent(); + + JClassType initializableInterface = typeOracle.findType(ServerRpc.class + .getCanonicalName()); + + for (JClassType rpcType : initializableInterface.getSubtypes()) { + String rpcClassName = rpcType.getQualifiedSourceName(); + if (InitializableServerRpc.class.getCanonicalName().equals( + rpcClassName)) { + // InitializableClientToServerRpc is a special marker interface + // that should not get a generated class + continue; + } + sourceWriter.println("} else if (rpcInterface == " + rpcClassName + + ".class) {"); + sourceWriter.indent(); + sourceWriter.println(rpcClassName + " rpc = GWT.create(" + + rpcClassName + ".class);"); + sourceWriter.println("((" + InitializableServerRpc.class.getName() + + ") rpc).initRpc(connector);"); + sourceWriter.println("return (T) rpc;"); + sourceWriter.outdent(); + } + + sourceWriter.println("} else {"); + sourceWriter.indent(); + sourceWriter + .println("throw new IllegalArgumentException(\"No RpcInterface of type \"+ rpcInterface.getName() + \" was found.\");"); + sourceWriter.outdent(); + // End of if + sourceWriter.println("}"); + // End of method + sourceWriter.println("}"); + + // close generated class + sourceWriter.outdent(); + sourceWriter.println("}"); + // commit generated class + context.commit(logger, printWriter); + logger.log(Type.INFO, composer.getCreatedClassName() + " created in " + + (new Date().getTime() - date.getTime()) / 1000 + "seconds"); + + } +} diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java new file mode 100644 index 0000000000..ad4e513049 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java @@ -0,0 +1,142 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.widgetsetutils; + +import java.io.PrintWriter; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameter; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.communication.InitializableServerRpc; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +/** + * GWT generator that creates client side proxy classes for making RPC calls + * from the client to the server. + * + * GWT.create() calls for interfaces extending {@link ServerRpc} are affected, + * and a proxy implementation is created. Note that the init(...) method of the + * proxy must be called before the proxy is used. + * + * @since 7.0 + */ +public class RpcProxyGenerator extends Generator { + @Override + public String generate(TreeLogger logger, GeneratorContext ctx, + String requestedClassName) throws UnableToCompleteException { + logger.log(TreeLogger.DEBUG, "Running RpcProxyGenerator", null); + + TypeOracle typeOracle = ctx.getTypeOracle(); + assert (typeOracle != null); + + JClassType requestedType = typeOracle.findType(requestedClassName); + if (requestedType == null) { + logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + + requestedClassName + "'", null); + throw new UnableToCompleteException(); + } + + String generatedClassName = "ServerRpc_" + + requestedType.getName().replaceAll("[$.]", "_"); + + JClassType initializableInterface = typeOracle + .findType(InitializableServerRpc.class.getCanonicalName()); + + ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory( + requestedType.getPackage().getName(), generatedClassName); + composer.addImplementedInterface(requestedType.getQualifiedSourceName()); + composer.addImplementedInterface(initializableInterface + .getQualifiedSourceName()); + composer.addImport(MethodInvocation.class.getCanonicalName()); + + PrintWriter printWriter = ctx.tryCreate(logger, + composer.getCreatedPackage(), + composer.getCreatedClassShortName()); + if (printWriter != null) { + logger.log(Type.INFO, "Generating client proxy for RPC interface '" + + requestedType.getQualifiedSourceName() + "'"); + SourceWriter writer = composer.createSourceWriter(ctx, printWriter); + + // constructor + writer.println("public " + generatedClassName + "() {}"); + + // initialization etc. + writeCommonFieldsAndMethods(logger, writer, typeOracle); + + // actual proxy methods forwarding calls to the server + writeRemoteProxyMethods(logger, writer, typeOracle, requestedType, + requestedType.isClassOrInterface().getInheritableMethods()); + + // End of class + writer.outdent(); + writer.println("}"); + + ctx.commit(logger, printWriter); + } + + return composer.getCreatedClassName(); + } + + private void writeCommonFieldsAndMethods(TreeLogger logger, + SourceWriter writer, TypeOracle typeOracle) { + JClassType applicationConnectionClass = typeOracle + .findType(ApplicationConnection.class.getCanonicalName()); + + // fields + writer.println("private " + ServerConnector.class.getName() + + " connector;"); + + // init method from the RPC interface + writer.println("public void initRpc(" + ServerConnector.class.getName() + + " connector) {"); + writer.indent(); + writer.println("this.connector = connector;"); + writer.outdent(); + writer.println("}"); + } + + private static void writeRemoteProxyMethods(TreeLogger logger, + SourceWriter writer, TypeOracle typeOracle, + JClassType requestedType, JMethod[] methods) { + for (JMethod m : methods) { + writer.print(m.getReadableDeclaration(false, false, false, false, + true)); + writer.println(" {"); + writer.indent(); + + writer.print("connector.getConnection().addMethodInvocationToQueue(new MethodInvocation(connector.getConnectorId(), \"" + + requestedType.getQualifiedBinaryName() + "\", \""); + writer.print(m.getName()); + writer.print("\", new Object[] {"); + // new Object[] { ... } for parameters - autoboxing etc. by the + // compiler + JParameter[] parameters = m.getParameters(); + boolean first = true; + for (JParameter p : parameters) { + if (!first) { + writer.print(", "); + } + first = false; + + writer.print(p.getName()); + } + writer.println("}), true);"); + + writer.outdent(); + writer.println("}"); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java new file mode 100644 index 0000000000..d3ed9fe484 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java @@ -0,0 +1,272 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.widgetsetutils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.communication.JSONSerializer; +import com.vaadin.terminal.gwt.client.communication.JsonDecoder; +import com.vaadin.terminal.gwt.client.communication.JsonEncoder; +import com.vaadin.terminal.gwt.client.communication.SerializerMap; + +/** + * GWT generator for creating serializer classes for custom classes sent from + * server to client. + * + * Only fields with a correspondingly named setter are deserialized. + * + * @since 7.0 + */ +public class SerializerGenerator extends Generator { + + private static final String SUBTYPE_SEPARATOR = "___"; + private static String beanSerializerPackageName = SerializerMap.class + .getPackage().getName(); + + @Override + public String generate(TreeLogger logger, GeneratorContext context, + String beanTypeName) throws UnableToCompleteException { + JClassType beanType = context.getTypeOracle().findType(beanTypeName); + String beanSerializerClassName = getSerializerSimpleClassName(beanType); + try { + // Generate class source code + generateClass(logger, context, beanType, beanSerializerPackageName, + beanSerializerClassName); + } catch (Exception e) { + logger.log(TreeLogger.ERROR, "SerializerGenerator failed for " + + beanType.getQualifiedSourceName(), e); + throw new UnableToCompleteException(); + } + + // return the fully qualifed name of the class generated + return getFullyQualifiedSerializerClassName(beanType); + } + + /** + * Generate source code for a VaadinSerializer implementation. + * + * @param logger + * Logger object + * @param context + * Generator context + * @param beanType + * @param beanTypeName + * bean type for which the serializer is to be generated + * @param beanSerializerTypeName + * name of the serializer class to generate + */ + private void generateClass(TreeLogger logger, GeneratorContext context, + JClassType beanType, String serializerPackageName, + String serializerClassName) { + // get print writer that receives the source code + PrintWriter printWriter = null; + printWriter = context.tryCreate(logger, serializerPackageName, + serializerClassName); + + // print writer if null, source code has ALREADY been generated + if (printWriter == null) { + return; + } + Date date = new Date(); + TypeOracle typeOracle = context.getTypeOracle(); + String beanQualifiedSourceName = beanType.getQualifiedSourceName(); + logger.log(Type.DEBUG, "Processing serializable type " + + beanQualifiedSourceName + "..."); + + // init composer, set class properties, create source writer + ClassSourceFileComposerFactory composer = null; + composer = new ClassSourceFileComposerFactory(serializerPackageName, + serializerClassName); + composer.addImport(GWT.class.getName()); + composer.addImport(JSONArray.class.getName()); + // composer.addImport(JSONObject.class.getName()); + // composer.addImport(VPaintableMap.class.getName()); + composer.addImport(JsonDecoder.class.getName()); + // composer.addImport(VaadinSerializer.class.getName()); + + composer.addImplementedInterface(JSONSerializer.class.getName()); + + SourceWriter sourceWriter = composer.createSourceWriter(context, + printWriter); + sourceWriter.indent(); + + // Serializer + + // public JSONValue serialize(Object value, ConnectorMap idMapper, + // ApplicationConnection connection) { + sourceWriter.println("public " + JSONObject.class.getName() + + " serialize(" + Object.class.getName() + " value, " + + ConnectorMap.class.getName() + " idMapper, " + + ApplicationConnection.class.getName() + " connection) {"); + sourceWriter.indent(); + // MouseEventDetails castedValue = (MouseEventDetails) value; + sourceWriter.println(beanQualifiedSourceName + " castedValue = (" + + beanQualifiedSourceName + ") value;"); + // JSONObject json = new JSONObject(); + sourceWriter.println(JSONObject.class.getName() + " json = new " + + JSONObject.class.getName() + "();"); + + for (JMethod setterMethod : getSetters(beanType)) { + String setterName = setterMethod.getName(); + String fieldName = setterName.substring(3); // setZindex() -> ZIndex + String getterName = findGetter(beanType, setterMethod); + + if (getterName == null) { + logger.log(TreeLogger.ERROR, "No getter found for " + fieldName + + ". Serialization will likely fail"); + } + // json.put("button", + // JsonEncoder.encode(castedValue.getButton(), idMapper, + // connection)); + sourceWriter.println("json.put(\"" + fieldName + "\", " + + JsonEncoder.class.getName() + ".encode(castedValue." + + getterName + "(), idMapper, connection));"); + } + // return json; + sourceWriter.println("return json;"); + // } + sourceWriter.println("}"); + + // Deserializer + sourceWriter.println("public " + beanQualifiedSourceName + + " deserialize(" + JSONObject.class.getName() + " jsonValue, " + + ConnectorMap.class.getName() + " idMapper, " + + ApplicationConnection.class.getName() + " connection) {"); + sourceWriter.indent(); + + // VButtonState state = GWT.create(VButtonState.class); + sourceWriter.println(beanQualifiedSourceName + " state = GWT.create(" + + beanQualifiedSourceName + ".class);"); + for (JMethod method : getSetters(beanType)) { + String setterName = method.getName(); + String fieldName = setterName.substring(3); // setZIndex() -> ZIndex + JType setterParameterType = method.getParameterTypes()[0]; + + logger.log(Type.DEBUG, "* Processing field " + fieldName + " in " + + beanQualifiedSourceName + " (" + beanType.getName() + ")"); + + String jsonFieldName = "json_" + fieldName; + // JSONArray json_Height = (JSONArray) jsonValue.get("height"); + sourceWriter.println("JSONArray " + jsonFieldName + + " = (JSONArray) jsonValue.get(\"" + fieldName + "\");"); + + // state.setHeight((String) + // JsonDecoder.decodeValue(jsonFieldValue,idMapper, connection)); + + String fieldType; + JPrimitiveType primitiveType = setterParameterType.isPrimitive(); + if (primitiveType != null) { + // This is a primitive type -> must used the boxed type + fieldType = primitiveType.getQualifiedBoxedSourceName(); + } else { + fieldType = setterParameterType.getQualifiedSourceName(); + } + + sourceWriter.println("state." + setterName + "((" + fieldType + + ") " + JsonDecoder.class.getName() + ".decodeValue(" + + jsonFieldName + ", idMapper, connection));"); + } + + // return state; + sourceWriter.println("return state;"); + sourceWriter.println("}"); + sourceWriter.outdent(); + + // End of class + sourceWriter.println("}"); + sourceWriter.outdent(); + + // commit generated class + context.commit(logger, printWriter); + logger.log(TreeLogger.INFO, "Generated Serializer class " + + getFullyQualifiedSerializerClassName(beanType)); + + } + + private String findGetter(JClassType beanType, JMethod setterMethod) { + JType setterParameterType = setterMethod.getParameterTypes()[0]; + String fieldName = setterMethod.getName().substring(3); + if (setterParameterType.getQualifiedSourceName().equals( + boolean.class.getName())) { + return "is" + fieldName; + } else { + return "get" + fieldName; + } + } + + /** + * Returns a list of all setters found in the beanType or its parent class + * + * @param beanType + * The type to check + * @return A list of setter methods from the class and its parents + */ + protected static List<JMethod> getSetters(JClassType beanType) { + + List<JMethod> setterMethods = new ArrayList<JMethod>(); + + while (beanType != null + && !beanType.getQualifiedSourceName().equals( + Object.class.getName())) { + for (JMethod method : beanType.getMethods()) { + // Process all setters that have corresponding fields + if (!method.isPublic() || method.isStatic() + || !method.getName().startsWith("set") + || method.getParameterTypes().length != 1) { + // Not setter, skip to next method + continue; + } + setterMethods.add(method); + } + beanType = beanType.getSuperclass(); + } + + return setterMethods; + } + + private String decapitalize(String name) { + return name.substring(0, 1).toLowerCase() + name.substring(1); + } + + private static String getSerializerSimpleClassName(JClassType beanType) { + return getSimpleClassName(beanType) + "_Serializer"; + } + + private static String getSimpleClassName(JClassType type) { + if (type.isMemberType()) { + // Assumed to be static sub class + String baseName = getSimpleClassName(type.getEnclosingType()); + String name = baseName + SUBTYPE_SEPARATOR + + type.getSimpleSourceName(); + return name; + } + return type.getSimpleSourceName(); + } + + public static String getFullyQualifiedSerializerClassName(JClassType type) { + return beanSerializerPackageName + "." + + getSerializerSimpleClassName(type); + } +} diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java new file mode 100644 index 0000000000..013df4710c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java @@ -0,0 +1,320 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.widgetsetutils; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.JSONSerializer; +import com.vaadin.terminal.gwt.client.communication.SerializerMap; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.communication.SharedState; + +/** + * GWT generator that creates a {@link SerializerMap} implementation (mapper + * from type string to serializer instance) and serializer classes for all + * subclasses of {@link SharedState}. + * + * @since 7.0 + */ +public class SerializerMapGenerator extends Generator { + + private String packageName; + private String className; + + @Override + public String generate(TreeLogger logger, GeneratorContext context, + String typeName) throws UnableToCompleteException { + + try { + TypeOracle typeOracle = context.getTypeOracle(); + Set<JClassType> typesNeedingSerializers = findTypesNeedingSerializers( + typeOracle, logger); + warnIfNotJavaSerializable(typesNeedingSerializers, typeOracle, + logger); + Set<JClassType> typesWithExistingSerializers = findTypesWithExistingSerializers( + typeOracle, logger); + Set<JClassType> serializerMappings = new HashSet<JClassType>(); + serializerMappings.addAll(typesNeedingSerializers); + serializerMappings.addAll(typesWithExistingSerializers); + // get classType and save instance variables + JClassType classType = typeOracle.getType(typeName); + packageName = classType.getPackage().getName(); + className = classType.getSimpleSourceName() + "Impl"; + // Generate class source code for SerializerMapImpl + generateSerializerMap(serializerMappings, logger, context); + + SerializerGenerator sg = new SerializerGenerator(); + for (JClassType type : typesNeedingSerializers) { + sg.generate(logger, context, type.getQualifiedSourceName()); + } + } catch (Exception e) { + logger.log(TreeLogger.ERROR, + "SerializerMapGenerator creation failed", e); + throw new UnableToCompleteException(); + } + // return the fully qualifed name of the class generated + return packageName + "." + className; + } + + /** + * Emits a warning for all classes that are used in communication but do not + * implement java.io.Serializable. Implementing java.io.Serializable is not + * needed for communication but for the server side Application to be + * serializable i.e. work in GAE for instance. + * + * @param typesNeedingSerializers + * @param typeOracle + * @param logger + */ + private void warnIfNotJavaSerializable( + Set<JClassType> typesNeedingSerializers, TypeOracle typeOracle, + TreeLogger logger) { + JClassType javaSerializable = typeOracle.findType(Serializable.class + .getName()); + for (JClassType type : typesNeedingSerializers) { + boolean serializable = type.isAssignableTo(javaSerializable); + if (!serializable) { + logger.log( + Type.ERROR, + type + + " is used in RPC or shared state but does not implement " + + Serializable.class.getName() + + ". Communication will work but the Application on server side cannot be serialized if it refers to objects of this type."); + } + } + } + + private Set<JClassType> findTypesWithExistingSerializers( + TypeOracle typeOracle, TreeLogger logger) { + JClassType serializerInterface = typeOracle + .findType(JSONSerializer.class.getName()); + Set<JClassType> types = new HashSet<JClassType>(); + for (JClassType serializer : serializerInterface.getSubtypes()) { + JType[] deserializeParamTypes = new JType[] { + typeOracle.findType(JSONObject.class.getName()), + typeOracle.findType(ConnectorMap.class.getName()), + typeOracle.findType(ApplicationConnection.class.getName()) }; + JMethod deserializeMethod = serializer.findMethod("deserialize", + deserializeParamTypes); + if (deserializeMethod == null) { + continue; + } + + types.add(deserializeMethod.getReturnType().isClass()); + } + return types; + } + + /** + * Generate source code for SerializerMapImpl + * + * @param typesNeedingSerializers + * + * @param logger + * Logger object + * @param context + * Generator context + */ + private void generateSerializerMap(Set<JClassType> typesNeedingSerializers, + TreeLogger logger, GeneratorContext context) { + // get print writer that receives the source code + PrintWriter printWriter = null; + printWriter = context.tryCreate(logger, packageName, className); + // print writer if null, source code has ALREADY been generated + if (printWriter == null) { + return; + } + Date date = new Date(); + TypeOracle typeOracle = context.getTypeOracle(); + + // init composer, set class properties, create source writer + ClassSourceFileComposerFactory composer = null; + composer = new ClassSourceFileComposerFactory(packageName, className); + composer.addImport("com.google.gwt.core.client.GWT"); + composer.addImplementedInterface(SerializerMap.class.getName()); + SourceWriter sourceWriter = composer.createSourceWriter(context, + printWriter); + sourceWriter.indent(); + + sourceWriter.println("public " + JSONSerializer.class.getName() + + " getSerializer(String type) {"); + sourceWriter.indent(); + + // TODO cache serializer instances in a map + for (JClassType type : typesNeedingSerializers) { + sourceWriter.println("if (type.equals(\"" + + type.getQualifiedBinaryName() + "\")) {"); + sourceWriter.indent(); + String serializerName = SerializerGenerator + .getFullyQualifiedSerializerClassName(type); + sourceWriter.println("return GWT.create(" + serializerName + + ".class);"); + sourceWriter.outdent(); + sourceWriter.println("}"); + logger.log(Type.INFO, "Configured serializer (" + serializerName + + ") for " + type.getName()); + } + sourceWriter + .println("throw new RuntimeException(\"No serializer found for class \"+type);"); + sourceWriter.outdent(); + sourceWriter.println("}"); + + // close generated class + sourceWriter.outdent(); + sourceWriter.println("}"); + // commit generated class + context.commit(logger, printWriter); + logger.log(Type.INFO, + "Done. (" + (new Date().getTime() - date.getTime()) / 1000 + + "seconds)"); + + } + + public Set<JClassType> findTypesNeedingSerializers(TypeOracle typeOracle, + TreeLogger logger) { + logger.log(Type.DEBUG, "Detecting serializable data types..."); + + HashSet<JClassType> types = new HashSet<JClassType>(); + + // Generate serializer classes for each subclass of SharedState + JClassType serializerType = typeOracle.findType(SharedState.class + .getName()); + JClassType[] serializerSubtypes = serializerType.getSubtypes(); + for (JClassType type : serializerSubtypes) { + types.add(type); + } + + // Serializer classes might also be needed for RPC methods + for (Class<?> cls : new Class[] { ServerRpc.class, ClientRpc.class }) { + JClassType rpcType = typeOracle.findType(cls.getName()); + JClassType[] serverRpcSubtypes = rpcType.getSubtypes(); + for (JClassType type : serverRpcSubtypes) { + addMethodParameterTypes(type, types, logger); + } + } + + // Add all types used from/in the types + for (Object t : types.toArray()) { + findSubTypesNeedingSerializers((JClassType) t, types); + } + logger.log(Type.DEBUG, "Serializable data types: " + types.toString()); + + return types; + } + + private void addMethodParameterTypes(JClassType classContainingMethods, + Set<JClassType> types, TreeLogger logger) { + for (JMethod method : classContainingMethods.getMethods()) { + if (method.getName().equals("initRpc")) { + continue; + } + for (JType type : method.getParameterTypes()) { + addTypeIfNeeded(types, type); + } + } + } + + public void findSubTypesNeedingSerializers(JClassType type, + Set<JClassType> serializableTypes) { + // Find all setters and look at their parameter type to determine if a + // new serializer is needed + for (JMethod setterMethod : SerializerGenerator.getSetters(type)) { + // The one and only parameter for the setter + JType setterType = setterMethod.getParameterTypes()[0]; + addTypeIfNeeded(serializableTypes, setterType); + } + } + + private void addTypeIfNeeded(Set<JClassType> serializableTypes, JType type) { + if (serializableTypes.contains(type)) { + return; + } + JParameterizedType parametrized = type.isParameterized(); + if (parametrized != null) { + for (JClassType parameterType : parametrized.getTypeArgs()) { + addTypeIfNeeded(serializableTypes, parameterType); + } + } + + if (serializationHandledByFramework(type)) { + return; + } + + if (serializableTypes.contains(type)) { + return; + } + + JClassType typeClass = type.isClass(); + if (typeClass != null) { + // setterTypeClass is null at least for List<String>. It is + // possible that we need to handle the cases somehow, for + // instance for List<MyObject>. + serializableTypes.add(typeClass); + findSubTypesNeedingSerializers(typeClass, serializableTypes); + } + } + + Set<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>(); + { + frameworkHandledTypes.add(String.class); + frameworkHandledTypes.add(Boolean.class); + frameworkHandledTypes.add(Integer.class); + frameworkHandledTypes.add(Float.class); + frameworkHandledTypes.add(Double.class); + frameworkHandledTypes.add(Long.class); + frameworkHandledTypes.add(Enum.class); + frameworkHandledTypes.add(String[].class); + frameworkHandledTypes.add(Object[].class); + frameworkHandledTypes.add(Map.class); + frameworkHandledTypes.add(List.class); + frameworkHandledTypes.add(Set.class); + + } + + private boolean serializationHandledByFramework(JType setterType) { + // Some types are handled by the framework at the moment. See #8449 + // This method should be removed at some point. + if (setterType.isArray() != null) { + return true; + } + if (setterType.isEnum() != null) { + return true; + } + if (setterType.isPrimitive() != null) { + return true; + } + + String qualifiedName = setterType.getQualifiedSourceName(); + for (Class<?> cls : frameworkHandledTypes) { + if (qualifiedName.equals(cls.getName())) { + return true; + } + } + + return false; + } +} diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java index c1f9134e7b..6d4289b173 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java @@ -22,18 +22,20 @@ import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; -import com.vaadin.terminal.Paintable; -import com.vaadin.terminal.gwt.client.ui.VView; -import com.vaadin.ui.ClientWidget; -import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; +import com.vaadin.terminal.gwt.client.ui.root.RootConnector; +import com.vaadin.terminal.gwt.server.ClientConnector; /** * WidgetMapGenerator's are GWT generator to build WidgetMapImpl dynamically - * based on {@link ClientWidget} annotations available in workspace. By - * modifying the generator it is possible to do some fine tuning for the - * generated widgetset (aka client side engine). The components to be included - * in the client side engine can modified be overriding - * {@link #getUsedPaintables()}. + * based on {@link Connect} annotations available in workspace. By modifying the + * generator it is possible to do some fine tuning for the generated widgetset + * (aka client side engine). The components to be included in the client side + * engine can modified be overriding {@link #getUsedConnectors()}. * <p> * The generator also decides how the client side component implementations are * loaded to the browser. The default generator is @@ -41,11 +43,11 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * that loads all widget implementation on application initialization. This has * been the only option until Vaadin 6.4. * <p> - * This generator uses the loadStyle hints from the {@link ClientWidget} - * annotations. Depending on the {@link LoadStyle} used, the widget may be - * included in the initially loaded JavaScript, loaded when the application has - * started and there is no communication to server or lazy loaded when the - * implementation is absolutely needed. + * This generator uses the loadStyle hints from the {@link Connect} annotations. + * Depending on the {@link LoadStyle} used, the widget may be included in the + * initially loaded JavaScript, loaded when the application has started and + * there is no communication to server or lazy loaded when the implementation is + * absolutely needed. * <p> * The GWT module description file of the widgetset ( * <code>...Widgetset.gwt.xml</code>) can be used to define the @@ -69,6 +71,9 @@ import com.vaadin.ui.ClientWidget.LoadStyle; */ public class WidgetMapGenerator extends Generator { + private static String componentConnectorClassName = ComponentConnector.class + .getName(); + private String packageName; private String className; @@ -123,15 +128,15 @@ public class WidgetMapGenerator extends Generator { SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter); - Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation = getUsedPaintables(); + Collection<Class<? extends ComponentConnector>> connectors = getUsedConnectors(context + .getTypeOracle()); - validatePaintables(logger, context, paintablesHavingWidgetAnnotation); + validateConnectors(logger, connectors); + logConnectors(logger, context, connectors); // generator constructor source code - generateImplementationDetector(sourceWriter, - paintablesHavingWidgetAnnotation); - generateInstantiatorMethod(sourceWriter, - paintablesHavingWidgetAnnotation); + generateImplementationDetector(sourceWriter, connectors); + generateInstantiatorMethod(sourceWriter, connectors); // close generated class sourceWriter.outdent(); sourceWriter.println("}"); @@ -143,47 +148,43 @@ public class WidgetMapGenerator extends Generator { } - /** - * Verifies that all client side components are available for client side - * GWT module. - * - * @param logger - * @param context - * @param paintablesHavingWidgetAnnotation - */ - private void validatePaintables( - TreeLogger logger, - GeneratorContext context, - Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) { - TypeOracle typeOracle = context.getTypeOracle(); - - for (Iterator<Class<? extends Paintable>> iterator = paintablesHavingWidgetAnnotation - .iterator(); iterator.hasNext();) { - Class<? extends Paintable> class1 = iterator.next(); - - ClientWidget annotation = class1.getAnnotation(ClientWidget.class); - - if (typeOracle.findType(annotation.value().getName()) == null) { - // GWT widget not inherited - logger.log(Type.WARN, "Widget class " - + annotation.value().getName() - + " was not found. The component " + class1.getName() - + " will not be included in the widgetset."); - iterator.remove(); + private void validateConnectors(TreeLogger logger, + Collection<Class<? extends ComponentConnector>> connectors) { + + Iterator<Class<? extends ComponentConnector>> iter = connectors + .iterator(); + while (iter.hasNext()) { + Class<? extends ComponentConnector> connectorClass = iter.next(); + Connect annotation = connectorClass.getAnnotation(Connect.class); + if (!ClientConnector.class.isAssignableFrom(annotation.value())) { + logger.log( + Type.WARN, + "Connector class " + + annotation.value().getName() + + " defined in @Connect annotation is not a subclass of " + + ClientConnector.class.getName() + + ". The component connector " + + connectorClass.getName() + + " will not be included in the widgetset."); + iter.remove(); } - } + + } + + private void logConnectors(TreeLogger logger, GeneratorContext context, + Collection<Class<? extends ComponentConnector>> connectors) { logger.log(Type.INFO, - "Widget set will contain implementations for following components: "); + "Widget set will contain implementations for following component connectors: "); TreeSet<String> classNames = new TreeSet<String>(); HashMap<String, String> loadStyle = new HashMap<String, String>(); - for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) { - String className = class1.getCanonicalName(); + for (Class<? extends ComponentConnector> connectorClass : connectors) { + String className = connectorClass.getCanonicalName(); classNames.add(className); - if (getLoadStyle(class1) == LoadStyle.DEFERRED) { + if (getLoadStyle(connectorClass) == LoadStyle.DEFERRED) { loadStyle.put(className, "DEFERRED"); - } else if (getLoadStyle(class1) == LoadStyle.LAZY) { + } else if (getLoadStyle(connectorClass) == LoadStyle.LAZY) { loadStyle.put(className, "LAZY"); } @@ -206,57 +207,72 @@ public class WidgetMapGenerator extends Generator { * @return a collections of Vaadin components that will be added to * widgetset */ - protected Collection<Class<? extends Paintable>> getUsedPaintables() { - return ClassPathExplorer.getPaintablesHavingWidgetAnnotation(); + @SuppressWarnings("unchecked") + private Collection<Class<? extends ComponentConnector>> getUsedConnectors( + TypeOracle typeOracle) { + JClassType connectorType = typeOracle.findType(Connector.class + .getName()); + Collection<Class<? extends ComponentConnector>> connectors = new HashSet<Class<? extends ComponentConnector>>(); + for (JClassType jClassType : connectorType.getSubtypes()) { + Connect annotation = jClassType.getAnnotation(Connect.class); + if (annotation != null) { + try { + Class<? extends ComponentConnector> clazz = (Class<? extends ComponentConnector>) Class + .forName(jClassType.getQualifiedSourceName()); + connectors.add(clazz); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + return connectors; } /** * Returns true if the widget for given component will be lazy loaded by the * client. The default implementation reads the information from the - * {@link ClientWidget} annotation. + * {@link Connect} annotation. * <p> * The method can be overridden to optimize the widget loading mechanism. If * the Widgetset is wanted to be optimized for a network with a high latency * or for a one with a very fast throughput, it may be good to return false * for every component. * - * @param paintableType + * @param connector * @return true iff the widget for given component should be lazy loaded by * the client side engine */ - protected LoadStyle getLoadStyle(Class<? extends Paintable> paintableType) { - ClientWidget annotation = paintableType - .getAnnotation(ClientWidget.class); + protected LoadStyle getLoadStyle( + Class<? extends ComponentConnector> connector) { + Connect annotation = connector.getAnnotation(Connect.class); return annotation.loadStyle(); } private void generateInstantiatorMethod( SourceWriter sourceWriter, - Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) { + Collection<Class<? extends ComponentConnector>> connectorsHavingComponentAnnotation) { Collection<Class<?>> deferredWidgets = new LinkedList<Class<?>>(); // TODO detect if it would be noticably faster to instantiate with a // lookup with index than with the hashmap - sourceWriter - .println("public void ensureInstantiator(Class<? extends Paintable> classType) {"); + sourceWriter.println("public void ensureInstantiator(Class<? extends " + + componentConnectorClassName + "> classType) {"); sourceWriter.println("if(!instmap.containsKey(classType)){"); boolean first = true; - ArrayList<Class<? extends Paintable>> lazyLoadedWidgets = new ArrayList<Class<? extends Paintable>>(); - - HashSet<Class<? extends com.vaadin.terminal.gwt.client.Paintable>> widgetsWithInstantiator = new HashSet<Class<? extends com.vaadin.terminal.gwt.client.Paintable>>(); - - for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) { - ClientWidget annotation = class1.getAnnotation(ClientWidget.class); - Class<? extends com.vaadin.terminal.gwt.client.Paintable> clientClass = annotation - .value(); - if(widgetsWithInstantiator.contains(clientClass)) { + ArrayList<Class<? extends ComponentConnector>> lazyLoadedWidgets = new ArrayList<Class<? extends ComponentConnector>>(); + + HashSet<Class<? extends com.vaadin.terminal.gwt.client.ComponentConnector>> connectorsWithInstantiator = new HashSet<Class<? extends com.vaadin.terminal.gwt.client.ComponentConnector>>(); + + for (Class<? extends ComponentConnector> class1 : connectorsHavingComponentAnnotation) { + Class<? extends ComponentConnector> clientClass = class1; + if (connectorsWithInstantiator.contains(clientClass)) { continue; } - if (clientClass == VView.class) { - // VView's are not instantiated by widgetset + if (clientClass == RootConnector.class) { + // Roots are not instantiated by widgetset continue; } if (!first) { @@ -267,8 +283,10 @@ public class WidgetMapGenerator extends Generator { sourceWriter.print("if( classType == " + clientClass.getName() + ".class) {"); - String instantiator = "new WidgetInstantiator() {\n public Paintable get() {\n return GWT.create(" - + clientClass.getName() + ".class );\n}\n}\n"; + String instantiator = "new WidgetInstantiator() {\n public " + + componentConnectorClassName + + " get() {\n return GWT.create(" + clientClass.getName() + + ".class );\n}\n}\n"; LoadStyle loadStyle = getLoadStyle(class1); @@ -295,15 +313,16 @@ public class WidgetMapGenerator extends Generator { sourceWriter.print(");"); } sourceWriter.print("}"); - widgetsWithInstantiator.add(clientClass); + connectorsWithInstantiator.add(clientClass); } sourceWriter.println("}"); sourceWriter.println("}"); - sourceWriter - .println("public Class<? extends Paintable>[] getDeferredLoadedWidgets() {"); + sourceWriter.println("public Class<? extends " + + componentConnectorClassName + + ">[] getDeferredLoadedWidgets() {"); sourceWriter.println("return new Class[] {"); first = true; @@ -312,10 +331,7 @@ public class WidgetMapGenerator extends Generator { sourceWriter.println(","); } first = false; - ClientWidget annotation = class2.getAnnotation(ClientWidget.class); - Class<? extends com.vaadin.terminal.gwt.client.Paintable> value = annotation - .value(); - sourceWriter.print(value.getName() + ".class"); + sourceWriter.print(class2.getName() + ".class"); } sourceWriter.println("};"); @@ -328,11 +344,12 @@ public class WidgetMapGenerator extends Generator { // TODO an index of last ensured widget in array - sourceWriter - .println("public Paintable instantiate(Class<? extends Paintable> classType) {"); + sourceWriter.println("public " + componentConnectorClassName + + " instantiate(Class<? extends " + componentConnectorClassName + + "> classType) {"); sourceWriter.indent(); - sourceWriter - .println("Paintable p = super.instantiate(classType); if(p!= null) return p;"); + sourceWriter.println(componentConnectorClassName + + " p = super.instantiate(classType); if(p!= null) return p;"); sourceWriter.println("return instmap.get(classType).get();"); sourceWriter.outdent(); @@ -348,30 +365,36 @@ public class WidgetMapGenerator extends Generator { */ private void generateImplementationDetector( SourceWriter sourceWriter, - Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) { + Collection<Class<? extends ComponentConnector>> paintablesHavingWidgetAnnotation) { sourceWriter - .println("public Class<? extends Paintable> " - + "getImplementationByServerSideClassName(String fullyQualifiedName) {"); + .println("public Class<? extends " + + componentConnectorClassName + + "> " + + "getConnectorClassForServerSideClassName(String fullyQualifiedName) {"); sourceWriter.indent(); sourceWriter .println("fullyQualifiedName = fullyQualifiedName.intern();"); - for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) { - ClientWidget annotation = class1.getAnnotation(ClientWidget.class); - Class<? extends com.vaadin.terminal.gwt.client.Paintable> clientClass = annotation - .value(); + for (Class<? extends ComponentConnector> connectorClass : paintablesHavingWidgetAnnotation) { + Class<? extends ClientConnector> clientConnectorClass = getClientConnectorClass(connectorClass); sourceWriter.print("if ( fullyQualifiedName == \""); - sourceWriter.print(class1.getName()); + sourceWriter.print(clientConnectorClass.getName()); sourceWriter.print("\" ) { ensureInstantiator(" - + clientClass.getName() + ".class); return "); - sourceWriter.print(clientClass.getName()); + + connectorClass.getName() + ".class); return "); + sourceWriter.print(connectorClass.getName()); sourceWriter.println(".class;}"); sourceWriter.print("else "); } - sourceWriter - .println("return com.vaadin.terminal.gwt.client.ui.VUnknownComponent.class;"); + sourceWriter.println("return " + + UnknownComponentConnector.class.getName() + ".class;"); sourceWriter.outdent(); sourceWriter.println("}"); } + + private static Class<? extends ClientConnector> getClientConnectorClass( + Class<? extends ComponentConnector> connectorClass) { + Connect annotation = connectorClass.getAnnotation(Connect.class); + return (Class<? extends ClientConnector>) annotation.value(); + } } diff --git a/src/com/vaadin/tools/ReflectTools.java b/src/com/vaadin/tools/ReflectTools.java index 9f0667f90e..ea2afae301 100644 --- a/src/com/vaadin/tools/ReflectTools.java +++ b/src/com/vaadin/tools/ReflectTools.java @@ -3,6 +3,9 @@ */ package com.vaadin.tools; +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** @@ -37,4 +40,87 @@ public class ReflectTools { throw new ExceptionInInitializerError(e); } } + + /** + * Returns the value of the java field. + * <p> + * Uses getter if present, otherwise tries to access even private fields + * directly. + * + * @param object + * The object containing the field + * @param field + * The field we want to get the value for + * @return The value of the field in the object + * @throws InvocationTargetException + * If the value could not be retrieved + * @throws IllegalAccessException + * If the value could not be retrieved + * @throws IllegalArgumentException + * If the value could not be retrieved + */ + public static Object getJavaFieldValue(Object object, + java.lang.reflect.Field field) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + PropertyDescriptor pd; + try { + pd = new PropertyDescriptor(field.getName(), object.getClass()); + Method getter = pd.getReadMethod(); + if (getter != null) { + return getter.invoke(object, (Object[]) null); + } + } catch (IntrospectionException e1) { + // Ignore this and try to get directly using the field + } + + // Try to get the value or throw an exception + if (!field.isAccessible()) { + // Try to gain access even if field is private + field.setAccessible(true); + } + return field.get(object); + } + + /** + * Sets the value of a java field. + * <p> + * Uses setter if present, otherwise tries to access even private fields + * directly. + * + * @param object + * The object containing the field + * @param field + * The field we want to set the value for + * @param value + * The value to set + * @throws IllegalAccessException + * If the value could not be assigned to the field + * @throws IllegalArgumentException + * If the value could not be assigned to the field + * @throws InvocationTargetException + * If the value could not be assigned to the field + */ + public static void setJavaFieldValue(Object object, + java.lang.reflect.Field field, Object value) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + PropertyDescriptor pd; + try { + pd = new PropertyDescriptor(field.getName(), object.getClass()); + Method setter = pd.getWriteMethod(); + if (setter != null) { + // Exceptions are thrown forward if this fails + setter.invoke(object, value); + } + } catch (IntrospectionException e1) { + // Ignore this and try to set directly using the field + } + + // Try to set the value directly to the field or throw an exception + if (!field.isAccessible()) { + // Try to gain access even if field is private + field.setAccessible(true); + } + field.set(object, value); + } } diff --git a/src/com/vaadin/ui/AbsoluteLayout.java b/src/com/vaadin/ui/AbsoluteLayout.java index 7872e05774..9ba005f75a 100644 --- a/src/com/vaadin/ui/AbsoluteLayout.java +++ b/src/com/vaadin/ui/AbsoluteLayout.java @@ -4,20 +4,20 @@ package com.vaadin.ui; import java.io.Serializable; -import java.util.Collection; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.Map; import com.vaadin.event.LayoutEvents.LayoutClickEvent; import com.vaadin.event.LayoutEvents.LayoutClickListener; import com.vaadin.event.LayoutEvents.LayoutClickNotifier; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Sizeable; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.ui.VAbsoluteLayout; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.absolutelayout.AbsoluteLayoutServerRpc; +import com.vaadin.terminal.gwt.client.ui.absolutelayout.AbsoluteLayoutState; /** * AbsoluteLayout is a layout implementation that mimics html absolute @@ -25,31 +25,39 @@ import com.vaadin.terminal.gwt.client.ui.VAbsoluteLayout; * */ @SuppressWarnings("serial") -@ClientWidget(VAbsoluteLayout.class) public class AbsoluteLayout extends AbstractLayout implements LayoutClickNotifier { - private static final String CLICK_EVENT = EventId.LAYOUT_CLICK; - - // The components in the layout - private Collection<Component> components = new LinkedHashSet<Component>(); + private AbsoluteLayoutServerRpc rpc = new AbsoluteLayoutServerRpc() { + public void layoutClick(MouseEventDetails mouseDetails, + Connector clickedConnector) { + fireEvent(LayoutClickEvent.createEvent(AbsoluteLayout.this, + mouseDetails, clickedConnector)); + } + }; // Maps each component to a position - private Map<Component, ComponentPosition> componentToCoordinates = new HashMap<Component, ComponentPosition>(); + private LinkedHashMap<Component, ComponentPosition> componentToCoordinates = new LinkedHashMap<Component, ComponentPosition>(); /** * Creates an AbsoluteLayout with full size. */ public AbsoluteLayout() { + registerRpc(rpc); setSizeFull(); } + @Override + public AbsoluteLayoutState getState() { + return (AbsoluteLayoutState) super.getState(); + } + /** * Gets an iterator for going through all components enclosed in the * absolute layout. */ public Iterator<Component> getComponentIterator() { - return components.iterator(); + return componentToCoordinates.keySet().iterator(); } /** @@ -59,7 +67,7 @@ public class AbsoluteLayout extends AbstractLayout implements * @return the number of contained components */ public int getComponentCount() { - return components.size(); + return componentToCoordinates.size(); } /** @@ -69,8 +77,7 @@ public class AbsoluteLayout extends AbstractLayout implements public void replaceComponent(Component oldComponent, Component newComponent) { ComponentPosition position = getPosition(oldComponent); removeComponent(oldComponent); - addComponent(newComponent); - componentToCoordinates.put(newComponent, position); + addComponent(newComponent, position); } /* @@ -82,29 +89,7 @@ public class AbsoluteLayout extends AbstractLayout implements */ @Override public void addComponent(Component c) { - components.add(c); - try { - super.addComponent(c); - requestRepaint(); - } catch (IllegalArgumentException e) { - components.remove(c); - throw e; - } - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.ui.AbstractComponentContainer#removeComponent(com.vaadin.ui - * .Component) - */ - @Override - public void removeComponent(Component c) { - components.remove(c); - componentToCoordinates.remove(c); - super.removeComponent(c); - requestRepaint(); + addComponent(c, new ComponentPosition()); } /** @@ -122,28 +107,90 @@ public class AbsoluteLayout extends AbstractLayout implements * The css position string */ public void addComponent(Component c, String cssPosition) { + ComponentPosition position = new ComponentPosition(); + position.setCSSString(cssPosition); + addComponent(c, position); + } + + /** + * Adds the component using the given position. Ensures the position is only + * set if the component is added correctly. + * + * @param c + * The component to add + * @param position + * The position info for the component. Must not be null. + * @throws IllegalArgumentException + * If adding the component failed + */ + private void addComponent(Component c, ComponentPosition position) + throws IllegalArgumentException { /* * Create position instance and add it to componentToCoordinates map. We * need to do this before we call addComponent so the attachListeners * can access this position. #6368 */ - ComponentPosition position = new ComponentPosition(); - position.setCSSString(cssPosition); - componentToCoordinates.put(c, position); - + internalSetPosition(c, position); try { - addComponent(c); - + super.addComponent(c); } catch (IllegalArgumentException e) { - // Remove component coordinates if adding fails - componentToCoordinates.remove(c); + internalRemoveComponent(c); throw e; } + requestRepaint(); + } + + /** + * Removes the component from all internal data structures. Does not + * actually remove the component from the layout (this is assumed to have + * been done by the caller). + * + * @param c + * The component to remove + */ + private void internalRemoveComponent(Component c) { + componentToCoordinates.remove(c); + } + + @Override + public void updateState() { + super.updateState(); + + // This could be in internalRemoveComponent and internalSetComponent if + // Map<Connector,String> was supported. We cannot get the child + // connectorId unless the component is attached to the application so + // the String->String map cannot be populated in internal* either. + Map<String, String> connectorToPosition = new HashMap<String, String>(); + for (Component c : this) { + connectorToPosition.put(c.getConnectorId(), getPosition(c) + .getCSSString()); + } + getState().setConnectorToCssPosition(connectorToPosition); + + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.AbstractComponentContainer#removeComponent(com.vaadin.ui + * .Component) + */ + @Override + public void removeComponent(Component c) { + internalRemoveComponent(c); + super.removeComponent(c); + requestRepaint(); } /** * Gets the position of a component in the layout. Returns null if component * is not attached to the layout. + * <p> + * Note that you cannot update the position by updating this object. Call + * {@link #setPosition(Component, ComponentPosition)} with the updated + * {@link ComponentPosition} object. + * </p> * * @param component * The component which position is needed @@ -152,15 +199,36 @@ public class AbsoluteLayout extends AbstractLayout implements * layout. */ public ComponentPosition getPosition(Component component) { - if (component.getParent() != this) { - return null; - } else if (componentToCoordinates.containsKey(component)) { - return componentToCoordinates.get(component); - } else { - ComponentPosition coords = new ComponentPosition(); - componentToCoordinates.put(component, coords); - return coords; + return componentToCoordinates.get(component); + } + + /** + * Sets the position of a component in the layout. + * + * @param component + * @param position + */ + public void setPosition(Component component, ComponentPosition position) { + if (!componentToCoordinates.containsKey(component)) { + throw new IllegalArgumentException( + "Component must be a child of this layout"); } + internalSetPosition(component, position); + } + + /** + * Updates the position for a component. Caller must ensure component is a + * child of this layout. + * + * @param component + * The component. Must be a child for this layout. Not enforced. + * @param position + * New position. Must not be null. + */ + private void internalSetPosition(Component component, + ComponentPosition position) { + componentToCoordinates.put(component, position); + requestRepaint(); } /** @@ -176,10 +244,10 @@ public class AbsoluteLayout extends AbstractLayout implements private Float bottomValue = null; private Float leftValue = null; - private int topUnits; - private int rightUnits; - private int bottomUnits; - private int leftUnits; + private Unit topUnits = Unit.PIXELS; + private Unit rightUnits = Unit.PIXELS; + private Unit bottomUnits = Unit.PIXELS; + private Unit leftUnits = Unit.PIXELS; /** * Sets the position attributes using CSS syntax. Attributes not @@ -193,7 +261,7 @@ public class AbsoluteLayout extends AbstractLayout implements */ public void setCSSString(String css) { topValue = rightValue = bottomValue = leftValue = null; - topUnits = rightUnits = bottomUnits = leftUnits = 0; + topUnits = rightUnits = bottomUnits = leftUnits = Unit.PIXELS; zIndex = -1; if (css == null) { return; @@ -215,24 +283,25 @@ public class AbsoluteLayout extends AbstractLayout implements } else { value = ""; } - String unit = value.replaceAll("[0-9\\.\\-]+", ""); - if (!unit.equals("")) { - value = value.substring(0, value.indexOf(unit)).trim(); + String symbol = value.replaceAll("[0-9\\.\\-]+", ""); + if (!symbol.equals("")) { + value = value.substring(0, value.indexOf(symbol)) + .trim(); } float v = Float.parseFloat(value); - int unitInt = parseCssUnit(unit); + Unit unit = Unit.getUnitFromSymbol(symbol); if (key.equals("top")) { topValue = v; - topUnits = unitInt; + topUnits = unit; } else if (key.equals("right")) { rightValue = v; - rightUnits = unitInt; + rightUnits = unit; } else if (key.equals("bottom")) { bottomValue = v; - bottomUnits = unitInt; + bottomUnits = unit; } else if (key.equals("left")) { leftValue = v; - leftUnits = unitInt; + leftUnits = unit; } } } @@ -240,23 +309,6 @@ public class AbsoluteLayout extends AbstractLayout implements } /** - * Parses a string and checks if a unit is found. If a unit is not found - * from the string the unit pixels is used. - * - * @param string - * The string to parse the unit from - * @return The found unit - */ - private int parseCssUnit(String string) { - for (int i = 0; i < UNIT_SYMBOLS.length; i++) { - if (UNIT_SYMBOLS[i].equals(string)) { - return i; - } - } - return 0; // defaults to px (eg. top:0;) - } - - /** * Converts the internal values into a valid CSS string. * * @return A valid CSS string @@ -264,16 +316,16 @@ public class AbsoluteLayout extends AbstractLayout implements public String getCSSString() { String s = ""; if (topValue != null) { - s += "top:" + topValue + UNIT_SYMBOLS[topUnits] + ";"; + s += "top:" + topValue + topUnits.getSymbol() + ";"; } if (rightValue != null) { - s += "right:" + rightValue + UNIT_SYMBOLS[rightUnits] + ";"; + s += "right:" + rightValue + rightUnits.getSymbol() + ";"; } if (bottomValue != null) { - s += "bottom:" + bottomValue + UNIT_SYMBOLS[bottomUnits] + ";"; + s += "bottom:" + bottomValue + bottomUnits.getSymbol() + ";"; } if (leftValue != null) { - s += "left:" + leftValue + UNIT_SYMBOLS[leftUnits] + ";"; + s += "left:" + leftValue + leftUnits.getSymbol() + ";"; } if (zIndex >= 0) { s += "z-index:" + zIndex + ";"; @@ -291,7 +343,7 @@ public class AbsoluteLayout extends AbstractLayout implements * The unit of the 'top' attribute. See UNIT_SYMBOLS for a * description of the available units. */ - public void setTop(Float topValue, int topUnits) { + public void setTop(Float topValue, Unit topUnits) { this.topValue = topValue; this.topUnits = topUnits; requestRepaint(); @@ -307,7 +359,7 @@ public class AbsoluteLayout extends AbstractLayout implements * The unit of the 'right' attribute. See UNIT_SYMBOLS for a * description of the available units. */ - public void setRight(Float rightValue, int rightUnits) { + public void setRight(Float rightValue, Unit rightUnits) { this.rightValue = rightValue; this.rightUnits = rightUnits; requestRepaint(); @@ -323,7 +375,7 @@ public class AbsoluteLayout extends AbstractLayout implements * The unit of the 'bottom' attribute. See UNIT_SYMBOLS for a * description of the available units. */ - public void setBottom(Float bottomValue, int bottomUnits) { + public void setBottom(Float bottomValue, Unit bottomUnits) { this.bottomValue = bottomValue; this.bottomUnits = bottomUnits; requestRepaint(); @@ -339,7 +391,7 @@ public class AbsoluteLayout extends AbstractLayout implements * The unit of the 'left' attribute. See UNIT_SYMBOLS for a * description of the available units. */ - public void setLeft(Float leftValue, int leftUnits) { + public void setLeft(Float leftValue, Unit leftUnits) { this.leftValue = leftValue; this.leftUnits = leftUnits; requestRepaint(); @@ -456,7 +508,7 @@ public class AbsoluteLayout extends AbstractLayout implements * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public int getTopUnits() { + public Unit getTopUnits() { return topUnits; } @@ -467,7 +519,7 @@ public class AbsoluteLayout extends AbstractLayout implements * See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public void setTopUnits(int topUnits) { + public void setTopUnits(Unit topUnits) { this.topUnits = topUnits; requestRepaint(); } @@ -478,7 +530,7 @@ public class AbsoluteLayout extends AbstractLayout implements * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public int getRightUnits() { + public Unit getRightUnits() { return rightUnits; } @@ -489,7 +541,7 @@ public class AbsoluteLayout extends AbstractLayout implements * See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public void setRightUnits(int rightUnits) { + public void setRightUnits(Unit rightUnits) { this.rightUnits = rightUnits; requestRepaint(); } @@ -500,7 +552,7 @@ public class AbsoluteLayout extends AbstractLayout implements * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public int getBottomUnits() { + public Unit getBottomUnits() { return bottomUnits; } @@ -511,7 +563,7 @@ public class AbsoluteLayout extends AbstractLayout implements * See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public void setBottomUnits(int bottomUnits) { + public void setBottomUnits(Unit bottomUnits) { this.bottomUnits = bottomUnits; requestRepaint(); } @@ -522,7 +574,7 @@ public class AbsoluteLayout extends AbstractLayout implements * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public int getLeftUnits() { + public Unit getLeftUnits() { return leftUnits; } @@ -533,7 +585,7 @@ public class AbsoluteLayout extends AbstractLayout implements * See {@link Sizeable} UNIT_SYMBOLS for a description of the * available units. */ - public void setLeftUnits(int leftUnits) { + public void setLeftUnits(Unit leftUnits) { this.leftUnits = leftUnits; requestRepaint(); } @@ -559,31 +611,15 @@ public class AbsoluteLayout extends AbstractLayout implements } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.ui.AbstractLayout#paintContent(com.vaadin.terminal.PaintTarget - * ) - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - for (Component component : components) { - target.startTag("cc"); - target.addAttribute("css", getPosition(component).getCSSString()); - component.paint(target); - target.endTag("cc"); - } - } - public void addListener(LayoutClickListener listener) { - addListener(CLICK_EVENT, LayoutClickEvent.class, listener, + addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener, LayoutClickListener.clickMethod); } public void removeListener(LayoutClickListener listener) { - removeListener(CLICK_EVENT, LayoutClickEvent.class, listener); + removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener); } } diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java index 0ca9cc6bd4..79a07ae00e 100644 --- a/src/com/vaadin/ui/AbstractComponent.java +++ b/src/com/vaadin/ui/AbstractComponent.java @@ -5,28 +5,41 @@ package com.vaadin.ui; import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.vaadin.Application; +import com.vaadin.event.ActionManager; import com.vaadin.event.EventRouter; import com.vaadin.event.MethodEventSource; +import com.vaadin.event.ShortcutListener; import com.vaadin.terminal.ErrorMessage; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.Terminal; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.server.ClientMethodInvocation; import com.vaadin.terminal.gwt.server.ComponentSizeValidator; +import com.vaadin.terminal.gwt.server.ResourceReference; +import com.vaadin.terminal.gwt.server.RpcManager; +import com.vaadin.terminal.gwt.server.RpcTarget; +import com.vaadin.terminal.gwt.server.ServerRpcManager; import com.vaadin.tools.ReflectTools; /** @@ -46,50 +59,15 @@ public abstract class AbstractComponent implements Component, MethodEventSource /* Private members */ /** - * Style names. - */ - private ArrayList<String> styles; - - /** - * Caption text. - */ - private String caption; - - /** * Application specific data object. The component does not use or modify * this. */ private Object applicationData; /** - * Icon to be shown together with caption. - */ - private Resource icon; - - /** - * Is the component enabled (its normal usage is allowed). - */ - private boolean enabled = true; - - /** - * Is the component visible (it is rendered). - */ - private boolean visible = true; - - /** - * Is the component read-only ? - */ - private boolean readOnly = false; - - /** - * Description of the usage (XML). - */ - private String description = null; - - /** * The container this component resides in. */ - private Component parent = null; + private HasComponents parent = null; /** * The EventRouter used for the event model. @@ -97,22 +75,11 @@ public abstract class AbstractComponent implements Component, MethodEventSource private EventRouter eventRouter = null; /** - * A set of event identifiers with registered listeners. - */ - private Set<String> eventIdentifiers = null; - - /** * The internal error message of the component. */ private ErrorMessage componentError = null; /** - * Immediate mode: if true, all variable changes are required to be sent - * from the terminal immediately. - */ - private boolean immediate = false; - - /** * Locale of this component. */ private Locale locale; @@ -127,24 +94,48 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ private LinkedList<RepaintRequestListener> repaintRequestListeners = null; - /** - * Are all the repaint listeners notified about recent changes ? - */ - private boolean repaintRequestListenersNotified = false; - - private String testingId; - /* Sizeable fields */ private float width = SIZE_UNDEFINED; private float height = SIZE_UNDEFINED; - private int widthUnit = UNITS_PIXELS; - private int heightUnit = UNITS_PIXELS; + private Unit widthUnit = Unit.PIXELS; + private Unit heightUnit = Unit.PIXELS; private static final Pattern sizePattern = Pattern .compile("^(-?\\d+(\\.\\d+)?)(%|px|em|ex|in|cm|mm|pt|pc)?$"); private ComponentErrorHandler errorHandler = null; + /** + * Keeps track of the Actions added to this component; the actual + * handling/notifying is delegated, usually to the containing window. + */ + private ActionManager actionManager; + + /** + * A map from client to server RPC interface class to the RPC call manager + * that handles incoming RPC calls for that interface. + */ + private Map<Class<?>, RpcManager> rpcManagerMap = new HashMap<Class<?>, RpcManager>(); + + /** + * A map from server to client RPC interface class to the RPC proxy that + * sends ourgoing RPC calls for that interface. + */ + private Map<Class<?>, ClientRpc> rpcProxyMap = new HashMap<Class<?>, ClientRpc>(); + + /** + * Shared state object to be communicated from the server to the client when + * modified. + */ + private ComponentState sharedState; + + /** + * Pending RPC method invocations to be sent. + */ + private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); + + private String connectorId; + /* Constructor */ /** @@ -157,11 +148,11 @@ public abstract class AbstractComponent implements Component, MethodEventSource /* Get/Set component properties */ public void setDebugId(String id) { - testingId = id; + getState().setDebugId(id); } public String getDebugId() { - return testingId; + return getState().getDebugId(); } /** @@ -179,8 +170,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource /** * Sets and replaces all previous style names of the component. This method - * will trigger a {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * will trigger a {@link RepaintRequestEvent}. * * @param style * the new style of the component. @@ -199,8 +189,9 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ public String getStyleName() { String s = ""; - if (styles != null) { - for (final Iterator<String> it = styles.iterator(); it.hasNext();) { + if (getState().getStyles() != null) { + for (final Iterator<String> it = getState().getStyles().iterator(); it + .hasNext();) { s += it.next(); if (it.hasNext()) { s += " "; @@ -216,13 +207,14 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ public void setStyleName(String style) { if (style == null || "".equals(style)) { - styles = null; + getState().setStyles(null); requestRepaint(); return; } - if (styles == null) { - styles = new ArrayList<String>(); + if (getState().getStyles() == null) { + getState().setStyles(new ArrayList<String>()); } + List<String> styles = getState().getStyles(); styles.clear(); String[] styleParts = style.split(" +"); for (String part : styleParts) { @@ -237,9 +229,18 @@ public abstract class AbstractComponent implements Component, MethodEventSource if (style == null || "".equals(style)) { return; } - if (styles == null) { - styles = new ArrayList<String>(); + if (style.contains(" ")) { + // Split space separated style names and add them one by one. + for (String realStyle : style.split(" ")) { + addStyleName(realStyle); + } + return; } + + if (getState().getStyles() == null) { + getState().setStyles(new ArrayList<String>()); + } + List<String> styles = getState().getStyles(); if (!styles.contains(style)) { styles.add(style); requestRepaint(); @@ -247,11 +248,11 @@ public abstract class AbstractComponent implements Component, MethodEventSource } public void removeStyleName(String style) { - if (styles != null) { + if (getState().getStyles() != null) { String[] styleParts = style.split(" +"); for (String part : styleParts) { if (part.length() > 0) { - styles.remove(part); + getState().getStyles().remove(part); } } requestRepaint(); @@ -263,20 +264,19 @@ public abstract class AbstractComponent implements Component, MethodEventSource * the default documentation from implemented interface. */ public String getCaption() { - return caption; + return getState().getCaption(); } /** * Sets the component's caption <code>String</code>. Caption is the visible * name of the component. This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * {@link RepaintRequestEvent}. * * @param caption * the new caption <code>String</code> for the component. */ public void setCaption(String caption) { - this.caption = caption; + getState().setCaption(caption); requestRepaint(); } @@ -319,6 +319,8 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ public void setLocale(Locale locale) { this.locale = locale; + + // FIXME: Reload value if there is a converter requestRepaint(); } @@ -327,58 +329,71 @@ public abstract class AbstractComponent implements Component, MethodEventSource * use the default documentation from implemented interface. */ public Resource getIcon() { - return icon; + ResourceReference ref = ((ResourceReference) getState().getIcon()); + if (ref == null) { + return null; + } else { + return ref.getResource(); + } } /** * Sets the component's icon. This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * {@link RepaintRequestEvent}. * * @param icon * the icon to be shown with the component's caption. */ public void setIcon(Resource icon) { - this.icon = icon; + if (icon == null) { + getState().setIcon(null); + } else { + getState().setIcon(new ResourceReference(icon)); + } requestRepaint(); } /* - * Tests if the component is enabled or not. Don't add a JavaDoc comment - * here, we use the default documentation from implemented interface. + * (non-Javadoc) + * + * @see com.vaadin.ui.Component#isEnabled() */ public boolean isEnabled() { - return enabled && (parent == null || parent.isEnabled()) && isVisible(); + return getState().isEnabled(); } /* - * Enables or disables the component. Don't add a JavaDoc comment here, we - * use the default documentation from implemented interface. + * (non-Javadoc) + * + * @see com.vaadin.ui.Component#setEnabled(boolean) */ public void setEnabled(boolean enabled) { - if (this.enabled != enabled) { - boolean wasEnabled = this.enabled; - boolean wasEnabledInContext = isEnabled(); - - this.enabled = enabled; - - boolean isEnabled = enabled; - boolean isEnabledInContext = isEnabled(); - - // If the actual enabled state (as rendered, in context) has not - // changed we do not need to repaint except if the parent is - // invisible. - // If the parent is invisible we must request a repaint so the - // component is repainted with the new enabled state when the parent - // is set visible again. This workaround is needed as isEnabled - // checks isVisible. - boolean needRepaint = (wasEnabledInContext != isEnabledInContext) - || (wasEnabled != isEnabled && (getParent() == null || !getParent() - .isVisible())); - - if (needRepaint) { - requestRepaint(); + if (getState().isEnabled() != enabled) { + getState().setEnabled(enabled); + requestRepaint(); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Connector#isConnectorEnabled() + */ + public boolean isConnectorEnabled() { + if (getParent() == null) { + // No parent -> the component cannot receive updates from the client + return false; + } else { + boolean thisEnabledAndVisible = isEnabled() && isVisible(); + if (!thisEnabledAndVisible) { + return false; + } + + if (!getParent().isConnectorEnabled()) { + return false; } + + return getParent().isComponentVisible(this); } } @@ -388,13 +403,12 @@ public abstract class AbstractComponent implements Component, MethodEventSource * interface. */ public boolean isImmediate() { - return immediate; + return getState().isImmediate(); } /** * Sets the component's immediate mode to the specified status. This method - * will trigger a {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * will trigger a {@link RepaintRequestEvent}. * * @param immediate * the boolean value specifying if the component should be in the @@ -402,7 +416,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @see Component#isImmediate() */ public void setImmediate(boolean immediate) { - this.immediate = immediate; + getState().setImmediate(immediate); requestRepaint(); } @@ -412,7 +426,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @see com.vaadin.ui.Component#isVisible() */ public boolean isVisible() { - return visible && (getParent() == null || getParent().isVisible()); + return getState().isVisible(); } /* @@ -421,14 +435,16 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @see com.vaadin.ui.Component#setVisible(boolean) */ public void setVisible(boolean visible) { + if (getState().isVisible() == visible) { + return; + } - if (this.visible != visible) { - this.visible = visible; - // Instead of requesting repaint normally we - // fire the event directly to assure that the - // event goes through event in the component might - // now be invisible - fireRequestRepaintEvent(null); + getState().setVisible(visible); + requestRepaint(); + if (getParent() != null) { + // Must always repaint the parent (at least the hierarchy) when + // visibility of a child component changes. + getParent().requestRepaint(); } } @@ -490,14 +506,13 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @return component's description <code>String</code> */ public String getDescription() { - return description; + return getState().getDescription(); } /** * Sets the component's description. See {@link #getDescription()} for more * information on what the description is. This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * {@link RepaintRequestEvent}. * * The description is displayed as HTML/XHTML in tooltips or directly in * certain components so care should be taken to avoid creating the @@ -507,7 +522,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * the new description string for the component. */ public void setDescription(String description) { - this.description = description; + getState().setDescription(description); requestRepaint(); } @@ -515,15 +530,40 @@ public abstract class AbstractComponent implements Component, MethodEventSource * Gets the component's parent component. Don't add a JavaDoc comment here, * we use the default documentation from implemented interface. */ - public Component getParent() { + public HasComponents getParent() { return parent; } + /** + * Returns the closest ancestor with the given type. + * <p> + * To find the Window that contains the component, use {@code Window w = + * getParent(Window.class);} + * </p> + * + * @param <T> + * The type of the ancestor + * @param parentType + * The ancestor class we are looking for + * @return The first ancestor that can be assigned to the given class. Null + * if no ancestor with the correct type could be found. + */ + public <T extends HasComponents> T findAncestor(Class<T> parentType) { + HasComponents p = getParent(); + while (p != null) { + if (parentType.isAssignableFrom(p.getClass())) { + return parentType.cast(p); + } + p = p.getParent(); + } + return null; + } + /* * Sets the parent component. Don't add a JavaDoc comment here, we use the * default documentation from implemented interface. */ - public void setParent(Component parent) { + public void setParent(HasComponents parent) { // If the parent is not changed, don't do anything if (parent == this.parent) { @@ -593,7 +633,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * here, we use the default documentation from implemented interface. */ public boolean isReadOnly() { - return readOnly; + return getState().isReadOnly(); } /* @@ -601,7 +641,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * use the default documentation from implemented interface. */ public void setReadOnly(boolean readOnly) { - this.readOnly = readOnly; + getState().setReadOnly(readOnly); requestRepaint(); } @@ -609,11 +649,11 @@ public abstract class AbstractComponent implements Component, MethodEventSource * Gets the parent window of the component. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ - public Window getWindow() { + public Root getRoot() { if (parent == null) { return null; } else { - return parent.getWindow(); + return parent.getRoot(); } } @@ -623,18 +663,12 @@ public abstract class AbstractComponent implements Component, MethodEventSource * interface. */ public void attach() { + getRoot().componentAttached(this); requestRepaint(); - if (!visible) { - /* - * Bypass the repaint optimization in childRequestedRepaint method - * when attaching. When reattaching (possibly moving) -> must - * repaint - */ - fireRequestRepaintEvent(null); - } if (delayedFocus) { focus(); } + setActionManagerViewer(); } /* @@ -642,6 +676,12 @@ public abstract class AbstractComponent implements Component, MethodEventSource * we use the default documentation from implemented interface. */ public void detach() { + if (actionManager != null) { + // Remove any existing viewer. Root cast is just to make the + // compiler happy + actionManager.setViewer((Root) null); + } + getRoot().componentDetached(this); } /** @@ -651,7 +691,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource if (this instanceof Focusable) { final Application app = getApplication(); if (app != null) { - getWindow().setFocusedComponent((Focusable) this); + getRoot().setFocusedComponent((Focusable) this); delayedFocus = false; } else { delayedFocus = true; @@ -686,115 +726,16 @@ public abstract class AbstractComponent implements Component, MethodEventSource } } - /* Component painting */ - - /* Documented in super interface */ - public void requestRepaintRequests() { - repaintRequestListenersNotified = false; - } - - /** - * - * <p> - * Paints the Paintable into a UIDL stream. This method creates the UIDL - * sequence describing it and outputs it to the given UIDL stream. - * </p> - * - * <p> - * It is called when the contents of the component should be painted in - * response to the component first being shown or having been altered so - * that its visual representation is changed. - * </p> - * - * <p> - * <b>Do not override this to paint your component.</b> Override - * {@link #paintContent(PaintTarget)} instead. - * </p> - * - * - * @param target - * the target UIDL stream where the component should paint itself - * to. - * @throws PaintException - * if the paint operation failed. - */ - public void paint(PaintTarget target) throws PaintException { - final String tag = target.getTag(this); - if (!target.startTag(this, tag) || repaintRequestListenersNotified) { - - // Paint the contents of the component - - // Only paint content of visible components. - if (isVisible()) { - if (getHeight() >= 0 - && (getHeightUnits() != UNITS_PERCENTAGE || ComponentSizeValidator - .parentCanDefineHeight(this))) { - target.addAttribute("height", "" + getCSSHeight()); - } - - if (getWidth() >= 0 - && (getWidthUnits() != UNITS_PERCENTAGE || ComponentSizeValidator - .parentCanDefineWidth(this))) { - target.addAttribute("width", "" + getCSSWidth()); - } - if (styles != null && styles.size() > 0) { - target.addAttribute("style", getStyle()); - } - if (isReadOnly()) { - target.addAttribute("readonly", true); - } - - if (isImmediate()) { - target.addAttribute("immediate", true); - } - if (!isEnabled()) { - target.addAttribute("disabled", true); - } - if (getCaption() != null) { - target.addAttribute("caption", getCaption()); - } - if (getIcon() != null) { - target.addAttribute("icon", getIcon()); - } - - if (getDescription() != null && getDescription().length() > 0) { - target.addAttribute("description", getDescription()); - } - - if (eventIdentifiers != null) { - target.addAttribute("eventListeners", - eventIdentifiers.toArray()); - } - - paintContent(target); - - final ErrorMessage error = getErrorMessage(); - if (error != null) { - error.paint(target); - } - } else { - target.addAttribute("invisible", true); - } - } else { - - // Contents have not changed, only cached presentation can be used - target.addAttribute("cached", true); - } - target.endTag(tag); - - repaintRequestListenersNotified = false; - } - /** * Build CSS compatible string representation of height. * * @return CSS height */ private String getCSSHeight() { - if (getHeightUnits() == UNITS_PIXELS) { - return ((int) getHeight()) + UNIT_SYMBOLS[getHeightUnits()]; + if (getHeightUnits() == Unit.PIXELS) { + return ((int) getHeight()) + getHeightUnits().getSymbol(); } else { - return getHeight() + UNIT_SYMBOLS[getHeightUnits()]; + return getHeight() + getHeightUnits().getSymbol(); } } @@ -804,47 +745,106 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @return CSS width */ private String getCSSWidth() { - if (getWidthUnits() == UNITS_PIXELS) { - return ((int) getWidth()) + UNIT_SYMBOLS[getWidthUnits()]; + if (getWidthUnits() == Unit.PIXELS) { + return ((int) getWidth()) + getWidthUnits().getSymbol(); } else { - return getWidth() + UNIT_SYMBOLS[getWidthUnits()]; + return getWidth() + getWidthUnits().getSymbol(); } } /** - * Paints any needed component-specific things to the given UIDL stream. The - * more general {@link #paint(PaintTarget)} method handles all general - * attributes common to all components, and it calls this method to paint - * any component-specific attributes to the UIDL stream. + * Returns the shared state bean with information to be sent from the server + * to the client. * - * @param target - * the target UIDL stream where the component should paint itself - * to - * @throws PaintException - * if the paint operation failed. + * Subclasses should override this method and set any relevant fields of the + * state returned by super.getState(). + * + * @since 7.0 + * + * @return updated component shared state + */ + public ComponentState getState() { + if (null == sharedState) { + sharedState = createState(); + } + return sharedState; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.Component#updateState() */ - public void paintContent(PaintTarget target) throws PaintException { + public void updateState() { + // TODO This logic should be on the client side and the state should + // simply be a data object with "width" and "height". + if (getHeight() >= 0 + && (getHeightUnits() != Unit.PERCENTAGE || ComponentSizeValidator + .parentCanDefineHeight(this))) { + getState().setHeight("" + getCSSHeight()); + } else { + getState().setHeight(""); + } + + if (getWidth() >= 0 + && (getWidthUnits() != Unit.PERCENTAGE || ComponentSizeValidator + .parentCanDefineWidth(this))) { + getState().setWidth("" + getCSSWidth()); + } else { + getState().setWidth(""); + } + ErrorMessage error = getErrorMessage(); + if (null != error) { + getState().setErrorMessage(error.getFormattedHtmlMessage()); + } else { + getState().setErrorMessage(null); + } } - /* Documentation copied from interface */ - public void requestRepaint() { + /** + * Creates the shared state bean to be used in server to client + * communication. + * <p> + * By default a state object of the defined return type of + * {@link #getState()} is created. Subclasses can override this method and + * return a new instance of the correct state class but this should rarely + * be necessary. + * </p> + * <p> + * No configuration of the values of the state should be performed in + * {@link #createState()}. + * + * @since 7.0 + * + * @return new shared state object + */ + protected ComponentState createState() { + try { + Method m = getClass().getMethod("getState", (Class[]) null); + Class<? extends ComponentState> type = (Class<? extends ComponentState>) m + .getReturnType(); + return type.newInstance(); + } catch (Exception e) { + getLogger().log( + Level.INFO, + "Error determining state object class for " + + getClass().getName()); + } - // The effect of the repaint request is identical to case where a - // child requests repaint - childRequestedRepaint(null); + // Fall back to ComponentState if detection fails for some reason. + return new ComponentState(); } /* Documentation copied from interface */ - public void childRequestedRepaint( - Collection<RepaintRequestListener> alreadyNotified) { + public void requestRepaint() { // Invisible components (by flag in this particular component) do not // need repaints - if (!visible) { + if (!getState().isVisible()) { return; } - fireRequestRepaintEvent(alreadyNotified); + fireRequestRepaintEvent(); } /** @@ -852,33 +852,15 @@ public abstract class AbstractComponent implements Component, MethodEventSource * * @param alreadyNotified */ - private void fireRequestRepaintEvent( - Collection<RepaintRequestListener> alreadyNotified) { - // Notify listeners only once - if (!repaintRequestListenersNotified) { - // Notify the listeners - if (repaintRequestListeners != null - && !repaintRequestListeners.isEmpty()) { - final Object[] listeners = repaintRequestListeners.toArray(); - final RepaintRequestEvent event = new RepaintRequestEvent(this); - for (int i = 0; i < listeners.length; i++) { - if (alreadyNotified == null) { - alreadyNotified = new LinkedList<RepaintRequestListener>(); - } - if (!alreadyNotified.contains(listeners[i])) { - ((RepaintRequestListener) listeners[i]) - .repaintRequested(event); - alreadyNotified - .add((RepaintRequestListener) listeners[i]); - repaintRequestListenersNotified = true; - } - } - } - - // Notify the parent - final Component parent = getParent(); - if (parent != null) { - parent.childRequestedRepaint(alreadyNotified); + // Notify listeners only once + private void fireRequestRepaintEvent() { + // Notify the listeners + if (repaintRequestListeners != null + && !repaintRequestListeners.isEmpty()) { + final Object[] listeners = repaintRequestListeners.toArray(); + final RepaintRequestEvent event = new RepaintRequestEvent(this); + for (int i = 0; i < listeners.length; i++) { + ((RepaintRequestListener) listeners[i]).repaintRequested(event); } } } @@ -903,17 +885,6 @@ public abstract class AbstractComponent implements Component, MethodEventSource } } - /* Component variable changes */ - - /* - * Invoked when the value of a variable has changed. Don't add a JavaDoc - * comment here, we use the default documentation from implemented - * interface. - */ - public void changeVariables(Object source, Map<String, Object> variables) { - - } - /* General event framework */ private static final Method COMPONENT_EVENT_METHOD = ReflectTools @@ -955,14 +926,11 @@ public abstract class AbstractComponent implements Component, MethodEventSource if (eventRouter == null) { eventRouter = new EventRouter(); } - if (eventIdentifiers == null) { - eventIdentifiers = new HashSet<String>(); - } boolean needRepaint = !eventRouter.hasListeners(eventType); eventRouter.addListener(eventType, target, method); if (needRepaint) { - eventIdentifiers.add(eventIdentifier); + getState().addRegisteredEventListener(eventIdentifier); requestRepaint(); } } @@ -1011,7 +979,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource if (eventRouter != null) { eventRouter.removeListener(eventType, target); if (!eventRouter.hasListeners(eventType)) { - eventIdentifiers.remove(eventIdentifier); + getState().removeRegisteredEventListener(eventIdentifier); requestRepaint(); } } @@ -1283,7 +1251,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * * @see com.vaadin.terminal.Sizeable#getHeightUnits() */ - public int getHeightUnits() { + public Unit getHeightUnits() { return heightUnit; } @@ -1301,36 +1269,19 @@ public abstract class AbstractComponent implements Component, MethodEventSource * * @see com.vaadin.terminal.Sizeable#getWidthUnits() */ - public int getWidthUnits() { + public Unit getWidthUnits() { return widthUnit; } /* * (non-Javadoc) * - * @see com.vaadin.terminal.Sizeable#setHeight(float) - */ - @Deprecated - public void setHeight(float height) { - setHeight(height, getHeightUnits()); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Sizeable#setHeightUnits(int) - */ - @Deprecated - public void setHeightUnits(int unit) { - setHeight(getHeight(), unit); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Sizeable#setHeight(float, int) + * @see com.vaadin.terminal.Sizeable#setHeight(float, Unit) */ - public void setHeight(float height, int unit) { + public void setHeight(float height, Unit unit) { + if (unit == null) { + throw new IllegalArgumentException("Unit can not be null"); + } this.height = height; heightUnit = unit; requestRepaint(); @@ -1343,8 +1294,8 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @see com.vaadin.terminal.Sizeable#setSizeFull() */ public void setSizeFull() { - setWidth(100, UNITS_PERCENTAGE); - setHeight(100, UNITS_PERCENTAGE); + setWidth(100, Unit.PERCENTAGE); + setHeight(100, Unit.PERCENTAGE); } /* @@ -1353,36 +1304,19 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @see com.vaadin.terminal.Sizeable#setSizeUndefined() */ public void setSizeUndefined() { - setWidth(-1, UNITS_PIXELS); - setHeight(-1, UNITS_PIXELS); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Sizeable#setWidth(float) - */ - @Deprecated - public void setWidth(float width) { - setWidth(width, getWidthUnits()); + setWidth(-1, Unit.PIXELS); + setHeight(-1, Unit.PIXELS); } /* * (non-Javadoc) * - * @see com.vaadin.terminal.Sizeable#setWidthUnits(int) + * @see com.vaadin.terminal.Sizeable#setWidth(float, Unit) */ - @Deprecated - public void setWidthUnits(int unit) { - setWidth(getWidth(), unit); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Sizeable#setWidth(float, int) - */ - public void setWidth(float width, int unit) { + public void setWidth(float width, Unit unit) { + if (unit == null) { + throw new IllegalArgumentException("Unit can not be null"); + } this.width = width; widthUnit = unit; requestRepaint(); @@ -1395,8 +1329,12 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @see com.vaadin.terminal.Sizeable#setWidth(java.lang.String) */ public void setWidth(String width) { - float[] p = parseStringSize(width); - setWidth(p[0], (int) p[1]); + Size size = parseStringSize(width); + if (size != null) { + setWidth(size.getSize(), size.getUnit()); + } else { + setWidth(-1, Unit.PIXELS); + } } /* @@ -1405,58 +1343,61 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @see com.vaadin.terminal.Sizeable#setHeight(java.lang.String) */ public void setHeight(String height) { - float[] p = parseStringSize(height); - setHeight(p[0], (int) p[1]); + Size size = parseStringSize(height); + if (size != null) { + setHeight(size.getSize(), size.getUnit()); + } else { + setHeight(-1, Unit.PIXELS); + } } /* * Returns array with size in index 0 unit in index 1. Null or empty string - * will produce {-1,UNITS_PIXELS} + * will produce {-1,Unit#PIXELS} */ - private static float[] parseStringSize(String s) { - float[] values = { -1, UNITS_PIXELS }; + private static Size parseStringSize(String s) { if (s == null) { - return values; + return null; } s = s.trim(); if ("".equals(s)) { - return values; + return null; } - + float size = 0; + Unit unit = null; Matcher matcher = sizePattern.matcher(s); if (matcher.find()) { - values[0] = Float.parseFloat(matcher.group(1)); - if (values[0] < 0) { - values[0] = -1; + size = Float.parseFloat(matcher.group(1)); + if (size < 0) { + size = -1; + unit = Unit.PIXELS; } else { - String unit = matcher.group(3); - if (unit == null) { - values[1] = UNITS_PIXELS; - } else if (unit.equals("px")) { - values[1] = UNITS_PIXELS; - } else if (unit.equals("%")) { - values[1] = UNITS_PERCENTAGE; - } else if (unit.equals("em")) { - values[1] = UNITS_EM; - } else if (unit.equals("ex")) { - values[1] = UNITS_EX; - } else if (unit.equals("in")) { - values[1] = UNITS_INCH; - } else if (unit.equals("cm")) { - values[1] = UNITS_CM; - } else if (unit.equals("mm")) { - values[1] = UNITS_MM; - } else if (unit.equals("pt")) { - values[1] = UNITS_POINTS; - } else if (unit.equals("pc")) { - values[1] = UNITS_PICAS; - } + String symbol = matcher.group(3); + unit = Unit.getUnitFromSymbol(symbol); } } else { throw new IllegalArgumentException("Invalid size argument: \"" + s + "\" (should match " + sizePattern.pattern() + ")"); } - return values; + return new Size(size, unit); + } + + private static class Size implements Serializable { + float size; + Unit unit; + + public Size(float size, Unit unit) { + this.size = size; + this.unit = unit; + } + + public float getSize() { + return size; + } + + public Unit getUnit() { + return unit; + } } public interface ComponentErrorEvent extends Terminal.ErrorEvent { @@ -1517,4 +1458,206 @@ public abstract class AbstractComponent implements Component, MethodEventSource } -}
\ No newline at end of file + /* + * Actions + */ + + /** + * Gets the {@link ActionManager} used to manage the + * {@link ShortcutListener}s added to this {@link Field}. + * + * @return the ActionManager in use + */ + protected ActionManager getActionManager() { + if (actionManager == null) { + actionManager = new ActionManager(); + setActionManagerViewer(); + } + return actionManager; + } + + /** + * Set a viewer for the action manager to be the parent sub window (if the + * component is in a window) or the root (otherwise). This is still a + * simplification of the real case as this should be handled by the parent + * VOverlay (on the client side) if the component is inside an VOverlay + * component. + */ + private void setActionManagerViewer() { + if (actionManager != null && getRoot() != null) { + // Attached and has action manager + Window w = findAncestor(Window.class); + if (w != null) { + actionManager.setViewer(w); + } else { + actionManager.setViewer(getRoot()); + } + } + + } + + public void addShortcutListener(ShortcutListener shortcut) { + getActionManager().addAction(shortcut); + } + + public void removeShortcutListener(ShortcutListener shortcut) { + if (actionManager != null) { + actionManager.removeAction(shortcut); + } + } + + /** + * Registers an RPC interface implementation for this component. + * + * A component can listen to multiple RPC interfaces, and subclasses can + * register additional implementations. + * + * @since 7.0 + * + * @param implementation + * RPC interface implementation + * @param rpcInterfaceType + * RPC interface class for which the implementation should be + * registered + */ + protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) { + rpcManagerMap.put(rpcInterfaceType, new ServerRpcManager<T>( + implementation, rpcInterfaceType)); + } + + /** + * Registers an RPC interface implementation for this component. + * + * A component can listen to multiple RPC interfaces, and subclasses can + * register additional implementations. + * + * @since 7.0 + * + * @param implementation + * RPC interface implementation. Also used to deduce the type. + */ + protected <T extends ServerRpc> void registerRpc(T implementation) { + Class<?> cls = implementation.getClass(); + Class<?>[] interfaces = cls.getInterfaces(); + while (interfaces.length == 0) { + // Search upwards until an interface is found. It must be found as T + // extends ServerRpc + cls = cls.getSuperclass(); + interfaces = cls.getInterfaces(); + } + if (interfaces.length != 1 + || !(ServerRpc.class.isAssignableFrom(interfaces[0]))) { + throw new RuntimeException( + "Use registerRpc(T implementation, Class<T> rpcInterfaceType) if the Rpc implementation implements more than one interface"); + } + Class<T> type = (Class<T>) interfaces[0]; + registerRpc(implementation, type); + } + + /** + * Returns an RPC proxy for a given server to client RPC interface for this + * component. + * + * TODO more javadoc, subclasses, ... + * + * @param rpcInterface + * RPC interface type + * + * @since 7.0 + */ + public <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface) { + // create, initialize and return a dynamic proxy for RPC + try { + if (!rpcProxyMap.containsKey(rpcInterface)) { + Class<T> proxyClass = (Class) Proxy.getProxyClass( + rpcInterface.getClassLoader(), rpcInterface); + Constructor<T> constructor = proxyClass + .getConstructor(InvocationHandler.class); + T rpcProxy = constructor.newInstance(new RpcInvoicationHandler( + rpcInterface)); + // cache the proxy + rpcProxyMap.put(rpcInterface, rpcProxy); + } + return (T) rpcProxyMap.get(rpcInterface); + } catch (Exception e) { + // TODO exception handling? + throw new RuntimeException(e); + } + } + + private class RpcInvoicationHandler implements InvocationHandler, + Serializable { + + private String rpcInterfaceName; + + public RpcInvoicationHandler(Class<?> rpcInterface) { + rpcInterfaceName = rpcInterface.getName().replaceAll("\\$", "."); + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + addMethodInvocationToQueue(rpcInterfaceName, method, args); + // TODO no need to do full repaint if only RPC calls + requestRepaint(); + return null; + } + + } + + /** + * For internal use: adds a method invocation to the pending RPC call queue. + * + * @param interfaceName + * RPC interface name + * @param methodName + * RPC method name + * @param parameters + * RPC vall parameters + * + * @since 7.0 + */ + protected void addMethodInvocationToQueue(String interfaceName, + Method method, Object[] parameters) { + // add to queue + pendingInvocations.add(new ClientMethodInvocation(this, interfaceName, + method, parameters)); + } + + /** + * @see RpcTarget#getRpcManager(Class) + * + * @param rpcInterface + * RPC interface for which a call was made + * @return RPC Manager handling calls for the interface + * + * @since 7.0 + */ + public RpcManager getRpcManager(Class<?> rpcInterface) { + return rpcManagerMap.get(rpcInterface); + } + + public List<ClientMethodInvocation> retrievePendingRpcCalls() { + if (pendingInvocations.isEmpty()) { + return Collections.emptyList(); + } else { + List<ClientMethodInvocation> result = pendingInvocations; + pendingInvocations = new ArrayList<ClientMethodInvocation>(); + return Collections.unmodifiableList(result); + } + } + + public String getConnectorId() { + if (connectorId == null) { + if (getApplication() == null) { + throw new RuntimeException( + "Component must be attached to an application when getConnectorId() is called for the first time"); + } + connectorId = getApplication().createConnectorId(this); + } + return connectorId; + } + + private Logger getLogger() { + return Logger.getLogger(AbstractComponent.class.getName()); + } +} diff --git a/src/com/vaadin/ui/AbstractComponentContainer.java b/src/com/vaadin/ui/AbstractComponentContainer.java index 5d5218307a..1c857a03cd 100644 --- a/src/com/vaadin/ui/AbstractComponentContainer.java +++ b/src/com/vaadin/ui/AbstractComponentContainer.java @@ -215,18 +215,20 @@ public abstract class AbstractComponentContainer extends AbstractComponent } @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - if (getParent() != null && !getParent().isEnabled()) { - // some ancestor still disabled, don't update children + public void setVisible(boolean visible) { + if (getState().isVisible() == visible) { return; - } else { - requestRepaintAll(); } + + super.setVisible(visible); + // If the visibility state is toggled it might affect all children + // aswell, e.g. make container visible should make children visible if + // they were only hidden because the container was hidden. + requestRepaintAll(); } @Override - public void setWidth(float width, int unit) { + public void setWidth(float width, Unit unit) { /* * child tree repaints may be needed, due to our fall back support for * invalid relative sizes @@ -237,9 +239,9 @@ public abstract class AbstractComponentContainer extends AbstractComponent // children currently in invalid state may need repaint dirtyChildren = getInvalidSizedChildren(false); } else if ((width == SIZE_UNDEFINED && getWidth() != SIZE_UNDEFINED) - || (unit == UNITS_PERCENTAGE - && getWidthUnits() != UNITS_PERCENTAGE && !ComponentSizeValidator - .parentCanDefineWidth(this))) { + || (unit == Unit.PERCENTAGE + && getWidthUnits() != Unit.PERCENTAGE && !ComponentSizeValidator + .parentCanDefineWidth(this))) { /* * relative width children may get to invalid state if width becomes * invalid. Width may also become invalid if units become percentage @@ -326,7 +328,7 @@ public abstract class AbstractComponentContainer extends AbstractComponent } @Override - public void setHeight(float height, int unit) { + public void setHeight(float height, Unit unit) { /* * child tree repaints may be needed, due to our fall back support for * invalid relative sizes @@ -337,9 +339,9 @@ public abstract class AbstractComponentContainer extends AbstractComponent // children currently in invalid state may need repaint dirtyChildren = getInvalidSizedChildren(true); } else if ((height == SIZE_UNDEFINED && getHeight() != SIZE_UNDEFINED) - || (unit == UNITS_PERCENTAGE - && getHeightUnits() != UNITS_PERCENTAGE && !ComponentSizeValidator - .parentCanDefineHeight(this))) { + || (unit == Unit.PERCENTAGE + && getHeightUnits() != Unit.PERCENTAGE && !ComponentSizeValidator + .parentCanDefineHeight(this))) { /* * relative height children may get to invalid state if height * becomes invalid. Height may also become invalid if units become @@ -354,22 +356,56 @@ public abstract class AbstractComponentContainer extends AbstractComponent } public void requestRepaintAll() { - requestRepaint(); - for (Iterator<Component> childIterator = getComponentIterator(); childIterator - .hasNext();) { + requestRepaintAll(this); + } + + /** + * Helper that implements the logic needed by requestRepaintAll. Calls + * requestRepaintAll/requestRepaint for the component container and all its + * children recursively. + * + * @param container + */ + public static void requestRepaintAll(HasComponents container) { + container.requestRepaint(); + if (container instanceof Panel) { + Panel p = (Panel) container; + // #2924 Panel is invalid, really invalid. + // Panel.getComponentIterator returns the children of content, not + // of Panel... + if (p.getContent() != null) { + p.getContent().requestRepaint(); + } + } + for (Iterator<Component> childIterator = container + .getComponentIterator(); childIterator.hasNext();) { Component c = childIterator.next(); - if (c instanceof Form) { - // Form has children in layout, but is not ComponentContainer - c.requestRepaint(); - ((Form) c).getLayout().requestRepaintAll(); - } else if (c instanceof Table) { - ((Table) c).requestRepaintAll(); - } else if (c instanceof ComponentContainer) { - ((ComponentContainer) c).requestRepaintAll(); + if (c instanceof HasComponents) { + requestRepaintAll((HasComponents) c); } else { c.requestRepaint(); } } } + /** + * Returns an iterator for the child components. + * + * @return An iterator for the child components. + * @see #getComponentIterator() + * @since 7.0.0 + */ + public Iterator<Component> iterator() { + return getComponentIterator(); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.HasComponents#isComponentVisible(com.vaadin.ui.Component) + */ + public boolean isComponentVisible(Component childComponent) { + return true; + } }
\ No newline at end of file diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java index e1d3270225..4efed11e2c 100644 --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@ -6,25 +6,29 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; -import java.util.Map; +import java.util.List; +import java.util.logging.Logger; +import com.vaadin.Application; import com.vaadin.data.Buffered; import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.event.Action; -import com.vaadin.event.ActionManager; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutListener; +import com.vaadin.terminal.AbstractErrorMessage; import com.vaadin.terminal.CompositeErrorMessage; import com.vaadin.terminal.ErrorMessage; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.gwt.client.AbstractFieldState; /** * <p> @@ -53,21 +57,29 @@ import com.vaadin.terminal.PaintTarget; * @since 3.0 */ @SuppressWarnings("serial") -public abstract class AbstractField extends AbstractComponent implements Field, - Property.ReadOnlyStatusChangeListener, +public abstract class AbstractField<T> extends AbstractComponent implements + Field<T>, Property.ReadOnlyStatusChangeListener, Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier { /* Private members */ + private static final Logger logger = Logger.getLogger(AbstractField.class + .getName()); + /** * Value of the abstract field. */ - private Object value; + private T value; /** + * A converter used to convert from the data model type to the field type + * and vice versa. + */ + private Converter<T, Object> converter = null; + /** * Connected data-source. */ - private Property dataSource = null; + private Property<?> dataSource = null; /** * The list of validators. @@ -85,11 +97,6 @@ public abstract class AbstractField extends AbstractComponent implements Field, private boolean readThroughMode = true; /** - * Is the field modified but not committed. - */ - private boolean modified = false; - - /** * Flag to indicate that the field is currently committing its value to the * datasource. */ @@ -111,31 +118,20 @@ public abstract class AbstractField extends AbstractComponent implements Field, private boolean invalidCommitted = false; /** - * The tab order number of this field. - */ - private int tabIndex = 0; - - /** - * Required field. - */ - private boolean required = false; - - /** * The error message for the exception that is thrown when the field is * required but empty. */ private String requiredError = ""; /** - * Is automatic validation enabled. + * The error message that is shown when the field value cannot be converted. */ - private boolean validationVisible = true; + private String conversionError = "Could not convert value to {0}"; /** - * Keeps track of the Actions added to this component; the actual - * handling/notifying is delegated, usually to the containing window. + * Is automatic validation enabled. */ - private ActionManager actionManager; + private boolean validationVisible = true; private boolean valueWasModifiedByDataSourceDuringCommit; @@ -155,29 +151,6 @@ public abstract class AbstractField extends AbstractComponent implements Field, * Paints the field. Don't add a JavaDoc comment here, we use the default * documentation from the implemented interface. */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - - // The tab ordering number - if (getTabIndex() != 0) { - target.addAttribute("tabindex", getTabIndex()); - } - - // If the field is modified, but not committed, set modified attribute - if (isModified()) { - target.addAttribute("modified", true); - } - - // Adds the required attribute - if (!isReadOnly() && isRequired()) { - target.addAttribute("required", true); - } - - // Hide the error indicator if needed - if (shouldHideErrors()) { - target.addAttribute("hideErrors", true); - } - } /** * Returns true if the error indicator be hidden when painting the component @@ -191,15 +164,21 @@ public abstract class AbstractField extends AbstractComponent implements Field, * to show it when there are errors */ protected boolean shouldHideErrors() { - return isRequired() && isEmpty() && getComponentError() == null - && getErrorMessage() != null; + // getErrorMessage() can still return something else than null based on + // validation etc. + return isRequired() && isEmpty() && getComponentError() == null; } - /* - * Gets the field type Don't add a JavaDoc comment here, we use the default - * documentation from the implemented interface. + /** + * Returns the type of the Field. The methods <code>getValue</code> and + * <code>setValue</code> must be compatible with this type: one must be able + * to safely cast the value returned from <code>getValue</code> to the given + * type and pass any variable assignable to this type as an argument to + * <code>setValue</code>. + * + * @return the type of the Field */ - public abstract Class<?> getType(); + public abstract Class<? extends T> getType(); /** * The abstract field is read only also if the data source is in read only @@ -247,23 +226,21 @@ public abstract class AbstractField extends AbstractComponent implements Field, public void commit() throws Buffered.SourceException, InvalidValueException { if (dataSource != null && !dataSource.isReadOnly()) { if ((isInvalidCommitted() || isValid())) { - final Object newValue = getValue(); try { // Commits the value to datasource. valueWasModifiedByDataSourceDuringCommit = false; committingValueToDataSource = true; - dataSource.setValue(newValue); - + getPropertyDataSource().setValue(getConvertedValue()); } catch (final Throwable e) { // Sets the buffering state. - currentBufferedSourceException = new Buffered.SourceException( + SourceException sourceException = new Buffered.SourceException( this, e); - requestRepaint(); + setCurrentBufferedSourceException(sourceException); // Throws the source exception. - throw currentBufferedSourceException; + throw sourceException; } finally { committingValueToDataSource = false; } @@ -273,25 +250,19 @@ public abstract class AbstractField extends AbstractComponent implements Field, } } - boolean repaintNeeded = false; - // The abstract field is not modified anymore - if (modified) { - modified = false; - repaintNeeded = true; + if (isModified()) { + setModified(false); } // If successful, remove set the buffering state to be ok - if (currentBufferedSourceException != null) { - currentBufferedSourceException = null; - repaintNeeded = true; + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); } if (valueWasModifiedByDataSourceDuringCommit) { valueWasModifiedByDataSourceDuringCommit = false; fireValueChange(false); - } else if (repaintNeeded) { - requestRepaint(); } } @@ -304,19 +275,18 @@ public abstract class AbstractField extends AbstractComponent implements Field, if (dataSource != null) { // Gets the correct value from datasource - Object newValue; + T newFieldValue; try { // Discards buffer by overwriting from datasource - newValue = String.class == getType() ? dataSource.toString() - : dataSource.getValue(); + newFieldValue = convertFromDataSource(getDataSourceValue()); // If successful, remove set the buffering state to be ok - if (currentBufferedSourceException != null) { - currentBufferedSourceException = null; - requestRepaint(); + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); } } catch (final Throwable e) { + // FIXME: What should really be done here if conversion fails? // Sets the buffering state currentBufferedSourceException = new Buffered.SourceException( @@ -328,29 +298,58 @@ public abstract class AbstractField extends AbstractComponent implements Field, } final boolean wasModified = isModified(); - modified = false; + setModified(false); // If the new value differs from the previous one - if ((newValue == null && value != null) - || (newValue != null && !newValue.equals(value))) { - setInternalValue(newValue); + if (!equals(newFieldValue, getInternalValue())) { + setInternalValue(newFieldValue); fireValueChange(false); - } - - // If the value did not change, but the modification status did - else if (wasModified) { + } else if (wasModified) { + // If the value did not change, but the modification status did requestRepaint(); } } } + /** + * Gets the value from the data source. This is only here because of clarity + * in the code that handles both the data model value and the field value. + * + * @return The value of the property data source + */ + private Object getDataSourceValue() { + return dataSource.getValue(); + } + + /** + * Returns the field value. This is always identical to {@link #getValue()} + * and only here because of clarity in the code that handles both the data + * model value and the field value. + * + * @return The value of the field + */ + private T getFieldValue() { + // Give the value from abstract buffers if the field if possible + if (dataSource == null || !isReadThrough() || isModified()) { + return getInternalValue(); + } + + // There is no buffered value so use whatever the data model provides + return convertFromDataSource(getDataSourceValue()); + } + /* * Has the field been modified since the last commit()? Don't add a JavaDoc * comment here, we use the default documentation from the implemented * interface. */ public boolean isModified() { - return modified; + return getState().isModified(); + } + + private void setModified(boolean modified) { + getState().setModified(modified); + requestRepaint(); } /* @@ -361,11 +360,28 @@ public abstract class AbstractField extends AbstractComponent implements Field, return writeThroughMode; } - /* - * Sets the field's write-through mode to the specified status Don't add a - * JavaDoc comment here, we use the default documentation from the - * implemented interface. + /** + * Sets the field's write-through mode to the specified status. When + * switching the write-through mode on, a {@link #commit()} will be + * performed. + * + * @see #setBuffered(boolean) for an easier way to control read through and + * write through modes + * + * @param writeThrough + * Boolean value to indicate if the object should be in + * write-through mode after the call. + * @throws SourceException + * If the operation fails because of an exception is thrown by + * the data source. + * @throws InvalidValueException + * If the implicit commit operation fails because of a + * validation error. + * @deprecated Use {@link #setBuffered(boolean)} instead. Note that + * setReadThrough(true), setWriteThrough(true) equals + * setBuffered(false) */ + @Deprecated public void setWriteThrough(boolean writeThrough) throws Buffered.SourceException, InvalidValueException { if (writeThroughMode == writeThrough) { @@ -385,38 +401,96 @@ public abstract class AbstractField extends AbstractComponent implements Field, return readThroughMode; } - /* - * Sets the field's read-through mode to the specified status Don't add a - * JavaDoc comment here, we use the default documentation from the - * implemented interface. + /** + * Sets the field's read-through mode to the specified status. When + * switching read-through mode on, the object's value is updated from the + * data source. + * + * @see #setBuffered(boolean) for an easier way to control read through and + * write through modes + * + * @param readThrough + * Boolean value to indicate if the object should be in + * read-through mode after the call. + * + * @throws SourceException + * If the operation fails because of an exception is thrown by + * the data source. The cause is included in the exception. + * @deprecated Use {@link #setBuffered(boolean)} instead. Note that + * setReadThrough(true), setWriteThrough(true) equals + * setBuffered(false) */ + @Deprecated public void setReadThrough(boolean readThrough) throws Buffered.SourceException { if (readThroughMode == readThrough) { return; } readThroughMode = readThrough; - if (!isModified() && readThroughMode && dataSource != null) { - setInternalValue(String.class == getType() ? dataSource.toString() - : dataSource.getValue()); + if (!isModified() && readThroughMode && getPropertyDataSource() != null) { + setInternalValue(convertFromDataSource(getDataSourceValue())); fireValueChange(false); } } + /** + * Sets the buffered mode of this Field. + * <p> + * When the field is in buffered mode, changes will not be committed to the + * property data source until {@link #commit()} is called. + * </p> + * <p> + * Changing buffered mode will change the read through and write through + * state for the field. + * </p> + * <p> + * Mixing calls to {@link #setBuffered(boolean)} and + * {@link #setReadThrough(boolean)} or {@link #setWriteThrough(boolean)} is + * generally a bad idea. + * </p> + * + * @param buffered + * true if buffered mode should be turned on, false otherwise + */ + public void setBuffered(boolean buffered) { + setReadThrough(!buffered); + setWriteThrough(!buffered); + } + + /** + * Checks the buffered mode of this Field. + * <p> + * This method only returns true if both read and write buffering is used. + * + * @return true if buffered mode is on, false otherwise + */ + public boolean isBuffered() { + return !isReadThrough() && !isWriteThrough(); + } + /* Property interface implementation */ /** - * Returns the value of the Property in human readable textual format. + * Returns the (field) value converted to a String using toString(). * * @see java.lang.Object#toString() + * @deprecated Instead use {@link #getValue()} to get the value of the + * field, {@link #getConvertedValue()} to get the field value + * converted to the data model type or + * {@link #getPropertyDataSource()} .getValue() to get the value + * of the data source. */ + @Deprecated @Override public String toString() { - final Object value = getValue(); + logger.warning("You are using AbstractField.toString() to get the value for a " + + getClass().getSimpleName() + + ". This is not recommended and will not be supported in future versions."); + final Object value = getFieldValue(); if (value == null) { return null; } - return getValue().toString(); + return value.toString(); } /** @@ -424,67 +498,63 @@ public abstract class AbstractField extends AbstractComponent implements Field, * * <p> * This is the visible, modified and possible invalid value the user have - * entered to the field. In the read-through mode, the abstract buffer is - * also updated and validation is performed. + * entered to the field. * </p> * * <p> * Note that the object returned is compatible with getType(). For example, * if the type is String, this returns Strings even when the underlying - * datasource is of some other type. In order to access the datasources - * native type, use getPropertyDatasource().getValue() instead. + * datasource is of some other type. In order to access the converted value, + * use {@link #getConvertedValue()} and to access the value of the property + * data source, use {@link Property#getValue()} for the property data + * source. * </p> * * <p> - * Note that when you extend AbstractField, you must reimplement this method - * if datasource.getValue() is not assignable to class returned by getType() - * AND getType() is not String. In case of Strings, getValue() calls - * datasource.toString() instead of datasource.getValue(). + * Since Vaadin 7.0, no implicit conversions between other data types and + * String are performed, but a converter is used if set. * </p> * * @return the current value of the field. */ - public Object getValue() { - - // Give the value from abstract buffers if the field if possible - if (dataSource == null || !isReadThrough() || isModified()) { - return value; - } - - Object newValue = String.class == getType() ? dataSource.toString() - : dataSource.getValue(); - - return newValue; + public T getValue() { + return getFieldValue(); } /** * Sets the value of the field. * - * @param newValue + * @param newFieldValue * the New value of the field. * @throws Property.ReadOnlyException - * @throws Property.ConversionException */ - public void setValue(Object newValue) throws Property.ReadOnlyException, - Property.ConversionException { - setValue(newValue, false); + public void setValue(Object newFieldValue) + throws Property.ReadOnlyException, Converter.ConversionException { + // This check is needed as long as setValue accepts Object instead of T + if (newFieldValue != null) { + if (!getType().isAssignableFrom(newFieldValue.getClass())) { + throw new Converter.ConversionException("Value of type " + + newFieldValue.getClass() + " cannot be assigned to " + + getType().getName()); + } + } + setValue((T) newFieldValue, false); } /** * Sets the value of the field. * - * @param newValue + * @param newFieldValue * the New value of the field. * @param repaintIsNotNeeded * True iff caller is sure that repaint is not needed. * @throws Property.ReadOnlyException - * @throws Property.ConversionException */ - protected void setValue(Object newValue, boolean repaintIsNotNeeded) - throws Property.ReadOnlyException, Property.ConversionException { + protected void setValue(T newFieldValue, boolean repaintIsNotNeeded) + throws Property.ReadOnlyException, Converter.ConversionException, + InvalidValueException { - if ((newValue == null && value != null) - || (newValue != null && !newValue.equals(value))) { + if (!equals(newFieldValue, getInternalValue())) { // Read only fields can not be changed if (isReadOnly()) { @@ -493,24 +563,24 @@ public abstract class AbstractField extends AbstractComponent implements Field, // Repaint is needed even when the client thinks that it knows the // new state if validity of the component may change - if (repaintIsNotNeeded && (isRequired() || getValidators() != null)) { + if (repaintIsNotNeeded + && (isRequired() || getValidators() != null || getConverter() != null)) { repaintIsNotNeeded = false; } - // If invalid values are not allowed, the value must be checked if (!isInvalidAllowed()) { - final Collection<Validator> v = getValidators(); - if (v != null) { - for (final Iterator<Validator> i = v.iterator(); i - .hasNext();) { - (i.next()).validate(newValue); - } - } + /* + * If invalid values are not allowed the value must be validated + * before it is set. If validation fails, the + * InvalidValueException is thrown and the internal value is not + * updated. + */ + validate(newFieldValue); } // Changes the value - setInternalValue(newValue); - modified = dataSource != null; + setInternalValue(newFieldValue); + setModified(dataSource != null); valueWasModifiedByDataSourceDuringCommit = false; // In write through mode , try to commit @@ -520,10 +590,11 @@ public abstract class AbstractField extends AbstractComponent implements Field, // Commits the value to datasource committingValueToDataSource = true; - dataSource.setValue(newValue); + getPropertyDataSource().setValue( + convertToDataSource(newFieldValue)); // The buffer is now unmodified - modified = false; + setModified(false); } catch (final Throwable e) { @@ -540,9 +611,8 @@ public abstract class AbstractField extends AbstractComponent implements Field, } // If successful, remove set the buffering state to be ok - if (currentBufferedSourceException != null) { - currentBufferedSourceException = null; - requestRepaint(); + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); } if (valueWasModifiedByDataSourceDuringCommit) { @@ -559,6 +629,13 @@ public abstract class AbstractField extends AbstractComponent implements Field, } } + private static boolean equals(Object value1, Object value2) { + if (value1 == null) { + return value2 == null; + } + return value1.equals(value2); + } + /* External data source */ /** @@ -612,25 +689,42 @@ public abstract class AbstractField extends AbstractComponent implements Field, public void setPropertyDataSource(Property newDataSource) { // Saves the old value - final Object oldValue = value; + final Object oldValue = getInternalValue(); // Stop listening to the old data source removePropertyListeners(); // Sets the new data source dataSource = newDataSource; - + getState().setPropertyReadOnly( + dataSource == null ? false : dataSource.isReadOnly()); + + // Check if the current converter is compatible. + if (newDataSource != null + && (getConverter() == null || !newDataSource.getType() + .isAssignableFrom(getConverter().getModelType()))) { + // Changing from e.g. Number -> Double should set a new converter, + // changing from Double -> Number can keep the old one (Property + // accepts Number) + + // Set a new converter if there is a new data source and + // there is no old converter or the old is incompatible. + setConverter(newDataSource.getType()); + } // Gets the value from source try { if (dataSource != null) { - setInternalValue(String.class == getType() ? dataSource - .toString() : dataSource.getValue()); + T fieldValue = convertFromDataSource(getDataSourceValue()); + setInternalValue(fieldValue); + } + setModified(false); + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); } - modified = false; } catch (final Throwable e) { - currentBufferedSourceException = new Buffered.SourceException(this, - e); - modified = true; + setCurrentBufferedSourceException(new Buffered.SourceException( + this, e)); + setModified(true); } // Listen to new data source if possible @@ -649,12 +743,159 @@ public abstract class AbstractField extends AbstractComponent implements Field, } // Fires value change if the value has changed + T value = getInternalValue(); if ((value != oldValue) && ((value != null && !value.equals(oldValue)) || value == null)) { fireValueChange(false); } } + /** + * Retrieves a converter for the field from the converter factory defined + * for the application. Clears the converter if no application reference is + * available or if the factory returns null. + * + * @param datamodelType + * The type of the data model that we want to be able to convert + * from + */ + public void setConverter(Class<?> datamodelType) { + Converter<T, ?> converter = null; + + Application app = Application.getCurrentApplication(); + if (app != null) { + ConverterFactory factory = app.getConverterFactory(); + converter = (Converter<T, ?>) factory.createConverter(getType(), + datamodelType); + } + setConverter(converter); + } + + /** + * Convert the given value from the data source type to the UI type. + * + * @param newValue + * The data source value to convert. + * @return The converted value that is compatible with the UI type or the + * original value if its type is compatible and no converter is set. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the data source type. + */ + @SuppressWarnings("unchecked") + private T convertFromDataSource(Object newValue) + throws Converter.ConversionException { + if (converter != null) { + return converter.convertToPresentation(newValue, getLocale()); + } + if (newValue == null) { + return null; + } + + if (getType().isAssignableFrom(newValue.getClass())) { + return (T) newValue; + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + newValue.getClass().getName() + + " to " + + getType() + + ". No converter is set and the types are not compatible."); + } + } + + /** + * Convert the given value from the UI type to the data source type. + * + * @param fieldValue + * The value to convert. Typically returned by + * {@link #getFieldValue()} + * @return The converted value that is compatible with the data source type. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the data source type. + */ + private Object convertToDataSource(T fieldValue) + throws Converter.ConversionException { + if (converter != null) { + /* + * If there is a converter, always use it. It must convert or throw + * an exception. + */ + try { + return converter.convertToModel(fieldValue, getLocale()); + } catch (com.vaadin.data.util.converter.Converter.ConversionException e) { + throw new Converter.ConversionException( + getConversionError(converter.getModelType()), e); + } + } + + if (fieldValue == null) { + // Null should always be passed through the converter but if there + // is no converter we can safely return null + return null; + } + + // check that the value class is compatible with the data source type + // (if data source set) or field type + Class<?> type; + if (getPropertyDataSource() != null) { + type = getPropertyDataSource().getType(); + } else { + type = getType(); + } + + if (type.isAssignableFrom(fieldValue.getClass())) { + return fieldValue; + } else { + throw new Converter.ConversionException(getConversionError(type)); + } + } + + /** + * Returns the conversion error with {0} replaced by the data source type. + * + * @param dataSourceType + * The type of the data source + * @return The value conversion error string with parameters replaced. + */ + protected String getConversionError(Class<?> dataSourceType) { + if (dataSourceType == null) { + return getConversionError(); + } else { + return getConversionError().replace("{0}", + dataSourceType.getSimpleName()); + } + } + + /** + * Returns the current value (as returned by {@link #getValue()}) converted + * to the data source type. + * <p> + * This returns the same as {@link AbstractField#getValue()} if no converter + * has been set. The value is not necessarily the same as the data source + * value e.g. if the field is in buffered mode and has been modified. + * </p> + * + * @return The converted value that is compatible with the data source type + */ + public Object getConvertedValue() { + return convertToDataSource(getFieldValue()); + } + + /** + * Sets the value of the field using a value of the data source type. The + * value given is converted to the field type and then assigned to the + * field. This will update the property data source in the same way as when + * {@link #setValue(Object)} is called. + * + * @param value + * The value to set. Must be the same type as the data source. + */ + public void setConvertedValue(Object value) { + setValue(convertFromDataSource(value)); + } + /* Validation */ /** @@ -713,102 +954,99 @@ public abstract class AbstractField extends AbstractComponent implements Field, * empty. If the field is empty it is considered valid if it is not required * and invalid otherwise. Validators are never checked for empty fields. * + * In most cases, {@link #validate()} should be used instead of + * {@link #isValid()} to also get the error message. + * * @return <code>true</code> if all registered validators claim that the * current value is valid or if the field is empty and not required, * <code>false</code> otherwise. */ public boolean isValid() { - if (isEmpty()) { - if (isRequired()) { - return false; - } else { - return true; - } - } - - if (validators == null) { + try { + validate(); return true; + } catch (InvalidValueException e) { + return false; } - - final Object value = getValue(); - for (final Iterator<Validator> i = validators.iterator(); i.hasNext();) { - if (!(i.next()).isValid(value)) { - return false; - } - } - - return true; } /** - * Checks the validity of the Validatable by validating the field with all - * attached validators except when the field is empty. An empty field is - * invalid if it is required and valid otherwise. + * Checks the validity of the Field. + * + * A field is invalid if it is set as required (using + * {@link #setRequired(boolean)} and is empty, if one or several of the + * validators added to the field indicate it is invalid or if the value + * cannot be converted provided a converter has been set. * * The "required" validation is a built-in validation feature. If the field - * is required, but empty, validation will throw an EmptyValueException with - * the error message set with setRequiredError(). + * is required and empty this method throws an EmptyValueException with the + * error message set using {@link #setRequiredError(String)}. * * @see com.vaadin.data.Validatable#validate() */ public void validate() throws Validator.InvalidValueException { - if (isEmpty()) { - if (isRequired()) { - throw new Validator.EmptyValueException(requiredError); - } else { - return; - } + if (isRequired() && isEmpty()) { + throw new Validator.EmptyValueException(requiredError); } + validate(getFieldValue()); + } - // If there is no validator, there can not be any errors - if (validators == null) { - return; - } + /** + * Validates that the given value pass the validators for the field. + * <p> + * This method does not check the requiredness of the field. + * + * @param fieldValue + * The value to check + * @throws Validator.InvalidValueException + * if one or several validators fail + */ + protected void validate(T fieldValue) + throws Validator.InvalidValueException { - // Initialize temps - Validator.InvalidValueException firstError = null; - LinkedList<InvalidValueException> errors = null; - final Object value = getValue(); + Object valueToValidate = fieldValue; - // Gets all the validation errors - for (final Iterator<Validator> i = validators.iterator(); i.hasNext();) { + // If there is a converter we start by converting the value as we want + // to validate the converted value + if (getConverter() != null) { try { - (i.next()).validate(value); - } catch (final Validator.InvalidValueException e) { - if (firstError == null) { - firstError = e; - } else { - if (errors == null) { - errors = new LinkedList<InvalidValueException>(); - errors.add(firstError); - } - errors.add(e); + valueToValidate = getConverter().convertToModel(fieldValue, + getLocale()); + } catch (Exception e) { + throw new InvalidValueException( + getConversionError(getConverter().getModelType())); + } + } + + List<InvalidValueException> validationExceptions = new ArrayList<InvalidValueException>(); + if (validators != null) { + // Gets all the validation errors + for (Validator v : validators) { + try { + v.validate(valueToValidate); + } catch (final Validator.InvalidValueException e) { + validationExceptions.add(e); } } } - // If there were no error - if (firstError == null) { + // If there were no errors + if (validationExceptions.isEmpty()) { return; } // If only one error occurred, throw it forwards - if (errors == null) { - throw firstError; + if (validationExceptions.size() == 1) { + throw validationExceptions.get(0); } - // Creates composite validator - final Validator.InvalidValueException[] exceptions = new Validator.InvalidValueException[errors - .size()]; - int index = 0; - for (final Iterator<InvalidValueException> i = errors.iterator(); i - .hasNext();) { - exceptions[index++] = i.next(); - } + InvalidValueException[] exceptionArray = validationExceptions + .toArray(new InvalidValueException[validationExceptions.size()]); - throw new Validator.InvalidValueException(null, exceptions); + // Create a composite validator and include all exceptions + throw new Validator.InvalidValueException(null, exceptionArray); } /** @@ -856,7 +1094,7 @@ public abstract class AbstractField extends AbstractComponent implements Field, * the requiredError string. For these fields the exclamation mark will * be hidden but the error must still be sent to the client. */ - ErrorMessage validationError = null; + Validator.InvalidValueException validationError = null; if (isValidationVisible()) { try { validate(); @@ -872,13 +1110,18 @@ public abstract class AbstractField extends AbstractComponent implements Field, // Return if there are no errors at all if (superError == null && validationError == null - && currentBufferedSourceException == null) { + && getCurrentBufferedSourceException() == null) { return null; } // Throw combination of the error types - return new CompositeErrorMessage(new ErrorMessage[] { superError, - validationError, currentBufferedSourceException }); + return new CompositeErrorMessage( + new ErrorMessage[] { + superError, + AbstractErrorMessage + .getErrorMessageForException(validationError), + AbstractErrorMessage + .getErrorMessageForException(getCurrentBufferedSourceException()) }); } @@ -952,6 +1195,7 @@ public abstract class AbstractField extends AbstractComponent implements Field, * @see Property.ReadOnlyStatusChangeListener */ public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) { + getState().setPropertyReadOnly(event.getProperty().isReadOnly()); requestRepaint(); } @@ -964,8 +1208,8 @@ public abstract class AbstractField extends AbstractComponent implements Field, * @VERSION@ * @since 3.0 */ - public class ReadOnlyStatusChangeEvent extends Component.Event implements - Property.ReadOnlyStatusChangeEvent, Serializable { + public static class ReadOnlyStatusChangeEvent extends Component.Event + implements Property.ReadOnlyStatusChangeEvent, Serializable { /** * New instance of text change event. @@ -1029,10 +1273,8 @@ public abstract class AbstractField extends AbstractComponent implements Field, public void valueChange(Property.ValueChangeEvent event) { if (isReadThrough()) { if (committingValueToDataSource) { - boolean propertyNotifiesOfTheBufferedValue = event - .getProperty().getValue() == value - || (value != null && value.equals(event.getProperty() - .getValue())); + boolean propertyNotifiesOfTheBufferedValue = equals(event + .getProperty().getValue(), getInternalValue()); if (!propertyNotifiesOfTheBufferedValue) { /* * Property (or chained property like PropertyFormatter) now @@ -1055,12 +1297,7 @@ public abstract class AbstractField extends AbstractComponent implements Field, } private void readValueFromProperty(Property.ValueChangeEvent event) { - setInternalValue(event.getProperty().getValue()); - } - - @Override - public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); + setInternalValue(convertFromDataSource(event.getProperty().getValue())); } /** @@ -1071,32 +1308,13 @@ public abstract class AbstractField extends AbstractComponent implements Field, super.focus(); } - /** - * Creates abstract field by the type of the property. - * - * <p> - * This returns most suitable field type for editing property of given type. - * </p> - * - * @param propertyType - * the Type of the property, that needs to be edited. - * @deprecated use e.g. - * {@link DefaultFieldFactory#createFieldByPropertyType(Class)} - * instead - */ - @Deprecated - public static AbstractField constructField(Class<?> propertyType) { - return (AbstractField) DefaultFieldFactory - .createFieldByPropertyType(propertyType); - } - /* * (non-Javadoc) * * @see com.vaadin.ui.Component.Focusable#getTabIndex() */ public int getTabIndex() { - return tabIndex; + return getState().getTabIndex(); } /* @@ -1105,20 +1323,39 @@ public abstract class AbstractField extends AbstractComponent implements Field, * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) */ public void setTabIndex(int tabIndex) { - this.tabIndex = tabIndex; + getState().setTabIndex(tabIndex); requestRepaint(); } /** + * Returns the internal field value, which might not match the data source + * value e.g. if the field has been modified and is not in write-through + * mode. + * + * This method can be overridden by subclasses together with + * {@link #setInternalValue(Object)} to compute internal field value at + * runtime. When doing so, typically also {@link #isModified()} needs to be + * overridden and care should be taken in the management of the empty state + * and buffering support. + * + * @return internal field value + */ + protected T getInternalValue() { + return value; + } + + /** * Sets the internal field value. This is purely used by AbstractField to * change the internal Field value. It does not trigger valuechange events. * It can be overridden by the inheriting classes to update all dependent * variables. * + * Subclasses can also override {@link #getInternalValue()} if necessary. + * * @param newValue * the new value to be set. */ - protected void setInternalValue(Object newValue) { + protected void setInternalValue(T newValue) { value = newValue; if (validators != null && !validators.isEmpty()) { requestRepaint(); @@ -1133,9 +1370,6 @@ public abstract class AbstractField extends AbstractComponent implements Field, @Override public void attach() { super.attach(); - if (actionManager != null) { - actionManager.setViewer(getWindow()); - } if (!isListeningToPropertyEvents) { addPropertyListeners(); @@ -1149,9 +1383,6 @@ public abstract class AbstractField extends AbstractComponent implements Field, @Override public void detach() { super.detach(); - if (actionManager != null) { - actionManager.setViewer((Window) null); - } // Stop listening to data source events on detach to avoid a potential // memory leak. See #6155. removePropertyListeners(); @@ -1170,11 +1401,11 @@ public abstract class AbstractField extends AbstractComponent implements Field, * field isEmpty() regardless of any attached validators. * * - * @return <code>true</code> if the field is required .otherwise + * @return <code>true</code> if the field is required, otherwise * <code>false</code>. */ public boolean isRequired() { - return required; + return getState().isRequired(); } /** @@ -1193,7 +1424,7 @@ public abstract class AbstractField extends AbstractComponent implements Field, * Is the field required. */ public void setRequired(boolean required) { - this.required = required; + getState().setRequired(required); requestRepaint(); } @@ -1216,13 +1447,36 @@ public abstract class AbstractField extends AbstractComponent implements Field, } /** + * Gets the error that is shown if the field value cannot be converted to + * the data source type. + * + * @return The error that is shown if conversion of the field value fails + */ + public String getConversionError() { + return conversionError; + } + + /** + * Sets the error that is shown if the field value cannot be converted to + * the data source type. If {0} is present in the message, it will be + * replaced by the simple name of the data source type. + * + * @param valueConversionError + * Message to be shown when conversion of the value fails + */ + public void setConversionError(String valueConversionError) { + this.conversionError = valueConversionError; + requestRepaint(); + } + + /** * Is the field empty? * * In general, "empty" state is same as null. As an exception, TextField * also treats empty string as "empty". */ protected boolean isEmpty() { - return (getValue() == null); + return (getFieldValue() == null); } /** @@ -1270,34 +1524,13 @@ public abstract class AbstractField extends AbstractComponent implements Field, requestRepaint(); } - /* - * Actions - */ - /** - * Gets the {@link ActionManager} used to manage the - * {@link ShortcutListener}s added to this {@link Field}. + * Gets the current buffered source exception. * - * @return the ActionManager in use + * @return The current source exception */ - protected ActionManager getActionManager() { - if (actionManager == null) { - actionManager = new ActionManager(); - if (getWindow() != null) { - actionManager.setViewer(getWindow()); - } - } - return actionManager; - } - - public void addShortcutListener(ShortcutListener shortcut) { - getActionManager().addAction(shortcut); - } - - public void removeShortcutListener(ShortcutListener shortcut) { - if (actionManager != null) { - actionManager.removeAction(shortcut); - } + protected Buffered.SourceException getCurrentBufferedSourceException() { + return currentBufferedSourceException; } /** @@ -1357,6 +1590,42 @@ public abstract class AbstractField extends AbstractComponent implements Field, } /** + * Gets the converter used to convert the property data source value to the + * field value. + * + * @return The converter or null if none is set. + */ + public Converter<T, Object> getConverter() { + return converter; + } + + /** + * Sets the converter used to convert the field value to property data + * source type. The converter must have a presentation type that matches the + * field type. + * + * @param converter + * The new converter to use. + */ + public void setConverter(Converter<T, ?> converter) { + this.converter = (Converter<T, Object>) converter; + requestRepaint(); + } + + @Override + public AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + + @Override + public void updateState() { + super.updateState(); + + // Hide the error indicator if needed + getState().setHideErrors(shouldHideErrors()); + } + + /** * Registers this as an event listener for events sent by the data source * (if any). Does nothing if * <code>isListeningToPropertyEvents == true</code>. @@ -1391,4 +1660,4 @@ public abstract class AbstractField extends AbstractComponent implements Field, isListeningToPropertyEvents = false; } } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/ui/AbstractLayout.java b/src/com/vaadin/ui/AbstractLayout.java index 378a59a4ad..4876b40265 100644 --- a/src/com/vaadin/ui/AbstractLayout.java +++ b/src/com/vaadin/ui/AbstractLayout.java @@ -4,14 +4,7 @@ package com.vaadin.ui; -import java.util.Map; - -import com.vaadin.event.LayoutEvents.LayoutClickEvent; -import com.vaadin.event.LayoutEvents.LayoutClickNotifier; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutState; import com.vaadin.ui.Layout.MarginHandler; /** @@ -27,10 +20,13 @@ import com.vaadin.ui.Layout.MarginHandler; public abstract class AbstractLayout extends AbstractComponentContainer implements Layout, MarginHandler { - private static final String CLICK_EVENT = EventId.LAYOUT_CLICK; - protected MarginInfo margins = new MarginInfo(false); + @Override + public AbstractLayoutState getState() { + return (AbstractLayoutState) super.getState(); + } + /* * (non-Javadoc) * @@ -38,6 +34,7 @@ public abstract class AbstractLayout extends AbstractComponentContainer */ public void setMargin(boolean enabled) { margins.setMargins(enabled); + getState().setMarginsBitmask(margins.getBitMask()); requestRepaint(); } @@ -57,6 +54,7 @@ public abstract class AbstractLayout extends AbstractComponentContainer */ public void setMargin(MarginInfo marginInfo) { margins.setMargins(marginInfo); + getState().setMarginsBitmask(margins.getBitMask()); requestRepaint(); } @@ -68,62 +66,8 @@ public abstract class AbstractLayout extends AbstractComponentContainer public void setMargin(boolean topEnabled, boolean rightEnabled, boolean bottomEnabled, boolean leftEnabled) { margins.setMargins(topEnabled, rightEnabled, bottomEnabled, leftEnabled); + getState().setMarginsBitmask(margins.getBitMask()); requestRepaint(); } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractComponent#paintContent(com.vaadin - * .terminal.PaintTarget) - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - - // Add margin info. Defaults to false. - target.addAttribute("margins", margins.getBitMask()); - - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, - * java.util.Map) - */ - @SuppressWarnings("unchecked") - @Override - public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); - // not all subclasses use these events - if (this instanceof LayoutClickNotifier - && variables.containsKey(CLICK_EVENT)) { - fireClick((Map<String, Object>) variables.get(CLICK_EVENT)); - } - - } - - /** - * Fire a layout click event. - * - * Note that this method is only used by the subclasses that implement - * {@link LayoutClickNotifier}, and can be overridden for custom click event - * firing. - * - * @param parameters - * The parameters received from the client side implementation - */ - protected void fireClick(Map<String, Object> parameters) { - MouseEventDetails mouseDetails = MouseEventDetails - .deSerialize((String) parameters.get("mouseDetails")); - Component clickedComponent = (Component) parameters.get("component"); - Component childComponent = clickedComponent; - while (childComponent != null && childComponent.getParent() != this) { - childComponent = childComponent.getParent(); - } - - fireEvent(new LayoutClickEvent(this, mouseDetails, clickedComponent, - childComponent)); - } - } diff --git a/src/com/vaadin/ui/AbstractMedia.java b/src/com/vaadin/ui/AbstractMedia.java index 9117bce997..09cfd5ff12 100644 --- a/src/com/vaadin/ui/AbstractMedia.java +++ b/src/com/vaadin/ui/AbstractMedia.java @@ -8,18 +8,22 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VMediaBase; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.ui.MediaBaseConnector; +import com.vaadin.terminal.gwt.client.ui.MediaBaseConnector.MediaControl; /** * Abstract base class for the HTML5 media components. * * @author Vaadin Ltd */ -public class AbstractMedia extends AbstractComponent { +public class AbstractMedia extends AbstractComponent implements + Vaadin6Component { private List<Resource> sources = new ArrayList<Resource>(); @@ -33,10 +37,6 @@ public class AbstractMedia extends AbstractComponent { private boolean muted; - private boolean pause; - - private boolean play; - /** * Sets a single media file as the source of the media component. * @@ -182,47 +182,35 @@ public class AbstractMedia extends AbstractComponent { * Pauses the media. */ public void pause() { - // cancel any possible play command - play = false; - - pause = true; - requestRepaint(); + getRpcProxy(MediaControl.class).pause(); } /** * Starts playback of the media. */ public void play() { - // cancel any possible pause command. - pause = false; - - play = true; - requestRepaint(); + getRpcProxy(MediaControl.class).play(); } - @Override public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - target.addAttribute(VMediaBase.ATTR_CONTROLS, isShowControls()); + target.addAttribute(MediaBaseConnector.ATTR_CONTROLS, isShowControls()); if (getAltText() != null) { - target.addAttribute(VMediaBase.ATTR_ALT_TEXT, getAltText()); + target.addAttribute(MediaBaseConnector.ATTR_ALT_TEXT, getAltText()); } - target.addAttribute(VMediaBase.ATTR_HTML, isHtmlContentAllowed()); - target.addAttribute(VMediaBase.ATTR_AUTOPLAY, isAutoplay()); + target.addAttribute(MediaBaseConnector.ATTR_HTML, + isHtmlContentAllowed()); + target.addAttribute(MediaBaseConnector.ATTR_AUTOPLAY, isAutoplay()); for (Resource r : getSources()) { - target.startTag(VMediaBase.TAG_SOURCE); - target.addAttribute(VMediaBase.ATTR_RESOURCE, r); - target.addAttribute(VMediaBase.ATTR_RESOURCE_TYPE, r.getMIMEType()); - target.endTag(VMediaBase.TAG_SOURCE); - } - target.addAttribute(VMediaBase.ATTR_MUTED, isMuted()); - if (play) { - target.addAttribute(VMediaBase.ATTR_PLAY, true); - play = false; - } - if (pause) { - target.addAttribute(VMediaBase.ATTR_PAUSE, true); - pause = false; + target.startTag(MediaBaseConnector.TAG_SOURCE); + target.addAttribute(MediaBaseConnector.ATTR_RESOURCE, r); + target.addAttribute(MediaBaseConnector.ATTR_RESOURCE_TYPE, + r.getMIMEType()); + target.endTag(MediaBaseConnector.TAG_SOURCE); } + target.addAttribute(MediaBaseConnector.ATTR_MUTED, isMuted()); + } + + public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Remove once Vaadin6Component is no longer implemented } } diff --git a/src/com/vaadin/ui/AbstractOrderedLayout.java b/src/com/vaadin/ui/AbstractOrderedLayout.java index fc3ef5056d..3606fa6572 100644 --- a/src/com/vaadin/ui/AbstractOrderedLayout.java +++ b/src/com/vaadin/ui/AbstractOrderedLayout.java @@ -15,13 +15,26 @@ import com.vaadin.event.LayoutEvents.LayoutClickNotifier; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Sizeable; -import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutServerRpc; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutState; @SuppressWarnings("serial") public abstract class AbstractOrderedLayout extends AbstractLayout implements - Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier { + Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier, + Vaadin6Component { - private static final String CLICK_EVENT = EventId.LAYOUT_CLICK; + private AbstractOrderedLayoutServerRpc rpc = new AbstractOrderedLayoutServerRpc() { + + public void layoutClick(MouseEventDetails mouseDetails, + Connector clickedConnector) { + fireEvent(LayoutClickEvent.createEvent(AbstractOrderedLayout.this, + mouseDetails, clickedConnector)); + } + }; public static final Alignment ALIGNMENT_DEFAULT = Alignment.TOP_LEFT; @@ -39,10 +52,14 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements private final Map<Component, Float> componentToExpandRatio = new HashMap<Component, Float>(); - /** - * Is spacing between contained components enabled. Defaults to false. - */ - private boolean spacing = false; + public AbstractOrderedLayout() { + registerRpc(rpc); + } + + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } /** * Add a component into this container. The component is added to the right @@ -54,7 +71,7 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements @Override public void addComponent(Component c) { // Add to components before calling super.addComponent - // so that it is available to AttachListeners + // so that it is available to AttachListeners components.add(c); try { super.addComponent(c); @@ -75,7 +92,7 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements public void addComponentAsFirst(Component c) { // If c is already in this, we must remove it before proceeding // see ticket #7668 - if(c.getParent() == this) { + if (c.getParent() == this) { removeComponent(c); } components.addFirst(c); @@ -100,9 +117,9 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements public void addComponent(Component c, int index) { // If c is already in this, we must remove it before proceeding // see ticket #7668 - if(c.getParent() == this) { + if (c.getParent() == this) { // When c is removed, all components after it are shifted down - if(index > getComponentIndex(c)) { + if (index > getComponentIndex(c)) { index--; } removeComponent(c); @@ -160,26 +177,16 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements * @throws PaintException * if the paint operation failed. */ - @Override public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - - // Add spacing attribute (omitted if false) - if (spacing) { - target.addAttribute("spacing", spacing); - } - - // Adds all items in all the locations - for (Component c : components) { - // Paint child component UIDL - c.paint(target); - } - // Add child component alignment info to layout tag target.addAttribute("alignments", componentToAlignment); target.addAttribute("expandRatios", componentToExpandRatio); } + public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Remove once Vaadin6Component is no longer implemented + } + /* Documented in superclass */ public void replaceComponent(Component oldComponent, Component newComponent) { @@ -275,8 +282,8 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements * * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean) */ - public void setSpacing(boolean enabled) { - spacing = enabled; + public void setSpacing(boolean spacing) { + getState().setSpacing(spacing); requestRepaint(); } @@ -285,18 +292,8 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements * * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing() */ - @Deprecated - public boolean isSpacingEnabled() { - return spacing; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing() - */ public boolean isSpacing() { - return spacing; + return getState().isSpacing(); } /** @@ -350,29 +347,15 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements return (ratio == null) ? 0 : ratio.floatValue(); } - /** - * Sets the component alignment using a short hand string notation. - * - * @deprecated Replaced by - * {@link #setComponentAlignment(Component, Alignment)} - * - * @param component - * A child component in this layout - * @param alignment - * A short hand notation described in {@link AlignmentUtils} - */ - @Deprecated - public void setComponentAlignment(Component component, String alignment) { - AlignmentUtils.setComponentAlignment(this, component, alignment); - } - public void addListener(LayoutClickListener listener) { - addListener(CLICK_EVENT, LayoutClickEvent.class, listener, + addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener, LayoutClickListener.clickMethod); } public void removeListener(LayoutClickListener listener) { - removeListener(CLICK_EVENT, LayoutClickEvent.class, listener); + removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener); } /** diff --git a/src/com/vaadin/ui/AbstractSelect.java b/src/com/vaadin/ui/AbstractSelect.java index bb49626741..e586810b2d 100644 --- a/src/com/vaadin/ui/AbstractSelect.java +++ b/src/com/vaadin/ui/AbstractSelect.java @@ -32,9 +32,11 @@ import com.vaadin.terminal.KeyMapper; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; +import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.ui.dd.VIsOverId; import com.vaadin.terminal.gwt.client.ui.dd.VItemIdIs; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; +import com.vaadin.ui.AbstractSelect.ItemCaptionMode; /** * <p> @@ -55,46 +57,91 @@ import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; * @since 5.0 */ @SuppressWarnings("serial") -public abstract class AbstractSelect extends AbstractField implements +// TODO currently cannot specify type more precisely in case of multi-select +public abstract class AbstractSelect extends AbstractField<Object> implements Container, Container.Viewer, Container.PropertySetChangeListener, Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier, - Container.ItemSetChangeListener { + Container.ItemSetChangeListener, Vaadin6Component { + + public enum ItemCaptionMode { + /** + * Item caption mode: Item's ID's <code>String</code> representation is + * used as caption. + */ + ID, + /** + * Item caption mode: Item's <code>String</code> representation is used + * as caption. + */ + ITEM, + /** + * Item caption mode: Index of the item is used as caption. The index + * mode can only be used with the containers implementing the + * {@link com.vaadin.data.Container.Indexed} interface. + */ + INDEX, + /** + * Item caption mode: If an Item has a caption it's used, if not, Item's + * ID's <code>String</code> representation is used as caption. <b>This + * is the default</b>. + */ + EXPLICIT_DEFAULTS_ID, + /** + * Item caption mode: Captions must be explicitly specified. + */ + EXPLICIT, + /** + * Item caption mode: Only icons are shown, captions are hidden. + */ + ICON_ONLY, + /** + * Item caption mode: Item captions are read from property specified + * with <code>setItemCaptionPropertyId</code>. + */ + PROPERTY; + } /** - * Item caption mode: Item's ID's <code>String</code> representation is used - * as caption. + * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead */ - public static final int ITEM_CAPTION_MODE_ID = 0; + @Deprecated + public static final ItemCaptionMode ITEM_CAPTION_MODE_ID = ItemCaptionMode.ID; + /** - * Item caption mode: Item's <code>String</code> representation is used as - * caption. + * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead */ - public static final int ITEM_CAPTION_MODE_ITEM = 1; + @Deprecated + public static final ItemCaptionMode ITEM_CAPTION_MODE_ITEM = ItemCaptionMode.ITEM; + /** - * Item caption mode: Index of the item is used as caption. The index mode - * can only be used with the containers implementing the - * {@link com.vaadin.data.Container.Indexed} interface. + * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead */ - public static final int ITEM_CAPTION_MODE_INDEX = 2; + @Deprecated + public static final ItemCaptionMode ITEM_CAPTION_MODE_INDEX = ItemCaptionMode.INDEX; + /** - * Item caption mode: If an Item has a caption it's used, if not, Item's - * ID's <code>String</code> representation is used as caption. <b>This is - * the default</b>. + * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead */ - public static final int ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = 3; + @Deprecated + public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = ItemCaptionMode.EXPLICIT_DEFAULTS_ID; + /** - * Item caption mode: Captions must be explicitly specified. + * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead */ - public static final int ITEM_CAPTION_MODE_EXPLICIT = 4; + @Deprecated + public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT = ItemCaptionMode.EXPLICIT; + /** - * Item caption mode: Only icons are shown, captions are hidden. + * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead */ - public static final int ITEM_CAPTION_MODE_ICON_ONLY = 5; + @Deprecated + public static final ItemCaptionMode ITEM_CAPTION_MODE_ICON_ONLY = ItemCaptionMode.ICON_ONLY; + /** - * Item caption mode: Item captions are read from property specified with - * <code>setItemCaptionPropertyId</code>. + * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead */ - public static final int ITEM_CAPTION_MODE_PROPERTY = 6; + @Deprecated + public static final ItemCaptionMode ITEM_CAPTION_MODE_PROPERTY = ItemCaptionMode.PROPERTY; /** * Interface for option filtering, used to filter options based on user @@ -159,7 +206,7 @@ public abstract class AbstractSelect extends AbstractField implements /** * Keymapper used to map key values. */ - protected KeyMapper itemIdMapper = new KeyMapper(); + protected KeyMapper<Object> itemIdMapper = new KeyMapper<Object>(); /** * Item icons. @@ -174,7 +221,7 @@ public abstract class AbstractSelect extends AbstractField implements /** * Item caption mode. */ - private int itemCaptionMode = ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID; + private ItemCaptionMode itemCaptionMode = ItemCaptionMode.EXPLICIT_DEFAULTS_ID; /** * Item caption source property id. @@ -276,12 +323,8 @@ public abstract class AbstractSelect extends AbstractField implements * @throws PaintException * if the paint operation failed. */ - @Override public void paintContent(PaintTarget target) throws PaintException { - // Paints field properties - super.paintContent(target); - // Paints select attributes if (isMultiSelect()) { target.addAttribute("selectmode", "multi"); @@ -382,9 +425,7 @@ public abstract class AbstractSelect extends AbstractField implements * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, * java.util.Map) */ - @Override public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); // New option entered (and it is allowed) if (isNewItemsAllowed()) { @@ -515,16 +556,9 @@ public abstract class AbstractSelect extends AbstractField implements // Sets the caption property, if used if (getItemCaptionPropertyId() != null) { - try { - getContainerProperty(newItemCaption, - getItemCaptionPropertyId()).setValue( - newItemCaption); - } catch (final Property.ConversionException ignored) { - /* - * The conversion exception is safely ignored, the - * caption is just missing - */ - } + getContainerProperty(newItemCaption, + getItemCaptionPropertyId()) + .setValue(newItemCaption); } if (isMultiSelect()) { Set values = new HashSet((Collection) getValue()); @@ -543,10 +577,7 @@ public abstract class AbstractSelect extends AbstractField implements * to the terminal or null if no items is visible. */ public Collection<?> getVisibleItemIds() { - if (isVisible()) { - return getItemIds(); - } - return null; + return getItemIds(); } /* Property methods */ @@ -614,8 +645,7 @@ public abstract class AbstractSelect extends AbstractField implements * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object) */ @Override - public void setValue(Object newValue) throws Property.ReadOnlyException, - Property.ConversionException { + public void setValue(Object newValue) throws Property.ReadOnlyException { if (newValue == getNullSelectionItemId()) { newValue = null; } @@ -641,7 +671,7 @@ public abstract class AbstractSelect extends AbstractField implements */ @Override protected void setValue(Object newValue, boolean repaintIsNotNeeded) - throws Property.ReadOnlyException, Property.ConversionException { + throws Property.ReadOnlyException { if (isMultiSelect()) { if (newValue == null) { @@ -729,7 +759,7 @@ public abstract class AbstractSelect extends AbstractField implements * * @see com.vaadin.data.Container#getContainerProperty(Object, Object) */ - public Property getContainerProperty(Object itemId, Object propertyId) { + public Property<?> getContainerProperty(Object itemId, Object propertyId) { return items.getContainerProperty(itemId, propertyId); } @@ -939,10 +969,13 @@ public abstract class AbstractSelect extends AbstractField implements } /** - * Sets the multiselect mode. Setting multiselect mode false may loose + * Sets the multiselect mode. Setting multiselect mode false may lose * selection information: if selected items set contains one or more * selected items, only one of the selected items is kept as selected. * + * Subclasses of AbstractSelect can choose not to support changing the + * multiselect mode, and may throw {@link UnsupportedOperationException}. + * * @param multiSelect * the New value of property multiSelect. */ @@ -1045,11 +1078,11 @@ public abstract class AbstractSelect extends AbstractField implements switch (getItemCaptionMode()) { - case ITEM_CAPTION_MODE_ID: + case ID: caption = itemId.toString(); break; - case ITEM_CAPTION_MODE_INDEX: + case INDEX: if (items instanceof Container.Indexed) { caption = String.valueOf(((Container.Indexed) items) .indexOfId(itemId)); @@ -1058,29 +1091,32 @@ public abstract class AbstractSelect extends AbstractField implements } break; - case ITEM_CAPTION_MODE_ITEM: + case ITEM: final Item i = getItem(itemId); if (i != null) { caption = i.toString(); } break; - case ITEM_CAPTION_MODE_EXPLICIT: + case EXPLICIT: caption = itemCaptions.get(itemId); break; - case ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID: + case EXPLICIT_DEFAULTS_ID: caption = itemCaptions.get(itemId); if (caption == null) { caption = itemId.toString(); } break; - case ITEM_CAPTION_MODE_PROPERTY: - final Property p = getContainerProperty(itemId, + case PROPERTY: + final Property<?> p = getContainerProperty(itemId, getItemCaptionPropertyId()); if (p != null) { - caption = p.toString(); + Object value = p.getValue(); + if (value != null) { + caption = value.toString(); + } } break; } @@ -1090,7 +1126,7 @@ public abstract class AbstractSelect extends AbstractField implements } /** - * Sets the icon for an item. + * Sets tqhe icon for an item. * * @param itemId * the id of the item to be assigned an icon. @@ -1125,7 +1161,7 @@ public abstract class AbstractSelect extends AbstractField implements return null; } - final Property ip = getContainerProperty(itemId, + final Property<?> ip = getContainerProperty(itemId, getItemIconPropertyId()); if (ip == null) { return null; @@ -1167,8 +1203,8 @@ public abstract class AbstractSelect extends AbstractField implements * @param mode * the One of the modes listed above. */ - public void setItemCaptionMode(int mode) { - if (ITEM_CAPTION_MODE_ID <= mode && mode <= ITEM_CAPTION_MODE_PROPERTY) { + public void setItemCaptionMode(ItemCaptionMode mode) { + if (mode != null) { itemCaptionMode = mode; requestRepaint(); } @@ -1202,7 +1238,7 @@ public abstract class AbstractSelect extends AbstractField implements * * @return the One of the modes listed above. */ - public int getItemCaptionMode() { + public ItemCaptionMode getItemCaptionMode() { return itemCaptionMode; } @@ -1216,7 +1252,9 @@ public abstract class AbstractSelect extends AbstractField implements * null resets the item caption mode to * <code>ITEM_CAPTION_EXPLICIT_DEFAULTS_ID</code>. * </p> - * + * <p> + * Note that the type of the property used for caption must be String + * </p> * <p> * Setting the property id to null disables this feature. The id is null by * default @@ -1691,7 +1729,7 @@ public abstract class AbstractSelect extends AbstractField implements public void addNotifierForItem(Object itemId) { switch (getItemCaptionMode()) { - case ITEM_CAPTION_MODE_ITEM: + case ITEM: final Item i = getItem(itemId); if (i == null) { return; @@ -1704,7 +1742,7 @@ public abstract class AbstractSelect extends AbstractField implements Collection<?> pids = i.getItemPropertyIds(); if (pids != null) { for (Iterator<?> it = pids.iterator(); it.hasNext();) { - Property p = i.getItemProperty(it.next()); + Property<?> p = i.getItemProperty(it.next()); if (p != null && p instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) p) @@ -1715,8 +1753,8 @@ public abstract class AbstractSelect extends AbstractField implements } break; - case ITEM_CAPTION_MODE_PROPERTY: - final Property p = getContainerProperty(itemId, + case PROPERTY: + final Property<?> p = getContainerProperty(itemId, getItemCaptionPropertyId()); if (p != null && p instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) p) diff --git a/src/com/vaadin/ui/AbstractSplitPanel.java b/src/com/vaadin/ui/AbstractSplitPanel.java index b507b88478..5205952621 100644 --- a/src/com/vaadin/ui/AbstractSplitPanel.java +++ b/src/com/vaadin/ui/AbstractSplitPanel.java @@ -7,15 +7,15 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Iterator; -import java.util.Map; import com.vaadin.event.ComponentEventListener; import com.vaadin.event.MouseEvents.ClickEvent; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VSplitPanel; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelRpc; +import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelState; +import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelState.SplitterState; import com.vaadin.tools.ReflectTools; /** @@ -29,21 +29,26 @@ import com.vaadin.tools.ReflectTools; * @VERSION@ * @since 6.5 */ -public abstract class AbstractSplitPanel extends AbstractLayout { +public abstract class AbstractSplitPanel extends AbstractComponentContainer { - private Component firstComponent; + private Unit posUnit; - private Component secondComponent; + private AbstractSplitPanelRpc rpc = new AbstractSplitPanelRpc() { - private float pos = 50; - - private int posUnit = UNITS_PERCENTAGE; - - private boolean posReversed = false; + public void splitterClick(MouseEventDetails mouseDetails) { + fireEvent(new SplitterClickEvent(AbstractSplitPanel.this, + mouseDetails)); + } - private boolean locked = false; + public void setSplitterPosition(float position) { + getState().getSplitterState().setPosition(position); + } + }; - private static final String SPLITTER_CLICK_EVENT = VSplitPanel.SPLITTER_CLICK_EVENT_IDENTIFIER; + public AbstractSplitPanel() { + registerRpc(rpc); + setSplitPosition(50, Unit.PERCENTAGE, false); + } /** * Modifiable and Serializable Iterator for the components, used by @@ -67,17 +72,17 @@ public abstract class AbstractSplitPanel extends AbstractLayout { } i++; if (i == 1) { - return firstComponent == null ? secondComponent - : firstComponent; + return (getFirstComponent() == null ? getSecondComponent() + : getFirstComponent()); } else if (i == 2) { - return secondComponent; + return getSecondComponent(); } return null; } public void remove() { if (i == 1) { - if (firstComponent != null) { + if (getFirstComponent() != null) { setFirstComponent(null); i = 0; } else { @@ -98,60 +103,83 @@ public abstract class AbstractSplitPanel extends AbstractLayout { */ @Override public void addComponent(Component c) { - if (firstComponent == null) { - firstComponent = c; - } else if (secondComponent == null) { - secondComponent = c; + if (getFirstComponent() == null) { + setFirstComponent(c); + } else if (getSecondComponent() == null) { + setSecondComponent(c); } else { throw new UnsupportedOperationException( "Split panel can contain only two components"); } - super.addComponent(c); - requestRepaint(); } + /** + * Sets the first component of this split panel. Depending on the direction + * the first component is shown at the top or to the left. + * + * @param c + * The component to use as first component + */ public void setFirstComponent(Component c) { - if (firstComponent == c) { + if (getFirstComponent() == c) { // Nothing to do return; } - if (firstComponent != null) { + if (getFirstComponent() != null) { // detach old - removeComponent(firstComponent); + removeComponent(getFirstComponent()); } - firstComponent = c; - super.addComponent(c); + getState().setFirstChild(c); + if (c != null) { + super.addComponent(c); + } + requestRepaint(); } + /** + * Sets the second component of this split panel. Depending on the direction + * the second component is shown at the bottom or to the left. + * + * @param c + * The component to use as first component + */ public void setSecondComponent(Component c) { - if (c == secondComponent) { + if (getSecondComponent() == c) { // Nothing to do return; } - if (secondComponent != null) { + if (getSecondComponent() != null) { // detach old - removeComponent(secondComponent); + removeComponent(getSecondComponent()); + } + getState().setSecondChild(c); + if (c != null) { + super.addComponent(c); } - secondComponent = c; - super.addComponent(c); requestRepaint(); } /** - * @return the first component of this SplitPanel. + * Gets the first component of this split panel. Depending on the direction + * this is either the component shown at the top or to the left. + * + * @return the first component of this split panel */ public Component getFirstComponent() { - return firstComponent; + return (Component) getState().getFirstChild(); } /** - * @return the second component of this SplitPanel. + * Gets the second component of this split panel. Depending on the direction + * this is either the component shown at the top or to the left. + * + * @return the second component of this split panel */ public Component getSecondComponent() { - return secondComponent; + return (Component) getState().getSecondChild(); } /** @@ -163,10 +191,10 @@ public abstract class AbstractSplitPanel extends AbstractLayout { @Override public void removeComponent(Component c) { super.removeComponent(c); - if (c == firstComponent) { - firstComponent = null; - } else if (c == secondComponent) { - secondComponent = null; + if (c == getFirstComponent()) { + getState().setFirstChild(null); + } else if (c == getSecondComponent()) { + getState().setSecondChild(null); } requestRepaint(); } @@ -188,58 +216,20 @@ public abstract class AbstractSplitPanel extends AbstractLayout { */ public int getComponentCount() { int count = 0; - if (firstComponent != null) { + if (getFirstComponent() != null) { count++; } - if (secondComponent != null) { + if (getSecondComponent() != null) { count++; } return count; } - /** - * Paints the content of this component. - * - * @param target - * the Paint Event. - * @throws PaintException - * if the paint operation failed. - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - - final String position = pos + UNIT_SYMBOLS[posUnit]; - - target.addAttribute("position", position); - - if (isLocked()) { - target.addAttribute("locked", true); - } - - target.addAttribute("reversed", posReversed); - - if (firstComponent != null) { - firstComponent.paint(target); - } else { - VerticalLayout temporaryComponent = new VerticalLayout(); - temporaryComponent.setParent(this); - temporaryComponent.paint(target); - } - if (secondComponent != null) { - secondComponent.paint(target); - } else { - VerticalLayout temporaryComponent = new VerticalLayout(); - temporaryComponent.setParent(this); - temporaryComponent.paint(target); - } - } - /* Documented in superclass */ public void replaceComponent(Component oldComponent, Component newComponent) { - if (oldComponent == firstComponent) { + if (oldComponent == getFirstComponent()) { setFirstComponent(newComponent); - } else if (oldComponent == secondComponent) { + } else if (oldComponent == getSecondComponent()) { setSecondComponent(newComponent); } requestRepaint(); @@ -254,7 +244,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * unit is percentage. */ public void setSplitPosition(float pos) { - setSplitPosition(pos, posUnit, true, false); + setSplitPosition(pos, posUnit, false); } /** @@ -270,7 +260,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * second region else it is measured by the first region */ public void setSplitPosition(float pos, boolean reverse) { - setSplitPosition(pos, posUnit, true, reverse); + setSplitPosition(pos, posUnit, reverse); } /** @@ -282,8 +272,8 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * @param unit * the unit (from {@link Sizeable}) in which the size is given. */ - public void setSplitPosition(float pos, int unit) { - setSplitPosition(pos, unit, true, false); + public void setSplitPosition(float pos, Unit unit) { + setSplitPosition(pos, unit, false); } /** @@ -299,8 +289,21 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * second region else it is measured by the first region * */ - public void setSplitPosition(float pos, int unit, boolean reverse) { - setSplitPosition(pos, unit, true, reverse); + public void setSplitPosition(float pos, Unit unit, boolean reverse) { + if (unit != Unit.PERCENTAGE && unit != Unit.PIXELS) { + throw new IllegalArgumentException( + "Only percentage and pixel units are allowed"); + } + if (unit != Unit.PERCENTAGE) { + pos = Math.round(pos); + } + SplitterState splitterState = getState().getSplitterState(); + splitterState.setPosition(pos); + splitterState.setPositionUnit(unit.getSymbol()); + splitterState.setPositionReversed(reverse); + posUnit = unit; + + requestRepaint(); } /** @@ -310,7 +313,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * @return position of the splitter */ public float getSplitPosition() { - return pos; + return getState().getSplitterState().getPosition(); } /** @@ -318,41 +321,11 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * * @return unit of position of the splitter */ - public int getSplitPositionUnit() { + public Unit getSplitPositionUnit() { return posUnit; } /** - * Moves the position of the splitter. - * - * @param pos - * the new size of the first region. Fractions are only allowed - * when unit is percentage. - * @param unit - * the unit (from {@link Sizeable}) in which the size is given. - * @param repaintNotNeeded - * true if client side needs to be updated. Use false if the - * position info has come from the client side, thus it already - * knows the position. - */ - private void setSplitPosition(float pos, int unit, boolean repaintNeeded, - boolean reverse) { - if (unit != UNITS_PERCENTAGE && unit != UNITS_PIXELS) { - throw new IllegalArgumentException( - "Only percentage and pixel units are allowed"); - } - if (unit != UNITS_PERCENTAGE) { - pos = Math.round(pos); - } - this.pos = pos; - posUnit = unit; - posReversed = reverse; - if (repaintNeeded) { - requestRepaint(); - } - } - - /** * Lock the SplitPanels position, disabling the user from dragging the split * handle. * @@ -360,7 +333,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * Set <code>true</code> if locked, <code>false</code> otherwise. */ public void setLocked(boolean locked) { - this.locked = locked; + getState().getSplitterState().setLocked(locked); requestRepaint(); } @@ -371,37 +344,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * @return <code>true</code> if locked, <code>false</code> otherwise. */ public boolean isLocked() { - return locked; - } - - /* - * Invoked when a variable of the component changes. Don't add a JavaDoc - * comment here, we use the default documentation from implemented - * interface. - */ - @SuppressWarnings("unchecked") - @Override - public void changeVariables(Object source, Map<String, Object> variables) { - - super.changeVariables(source, variables); - - if (variables.containsKey("position") && !isLocked()) { - Float newPos = (Float) variables.get("position"); - setSplitPosition(newPos, posUnit, posReversed); - } - - if (variables.containsKey(SPLITTER_CLICK_EVENT)) { - fireClick((Map<String, Object>) variables.get(SPLITTER_CLICK_EVENT)); - } - - } - - @Override - protected void fireClick(Map<String, Object> parameters) { - MouseEventDetails mouseDetails = MouseEventDetails - .deSerialize((String) parameters.get("mouseDetails")); - - fireEvent(new SplitterClickEvent(this, mouseDetails)); + return getState().getSplitterState().isLocked(); } /** @@ -436,12 +379,19 @@ public abstract class AbstractSplitPanel extends AbstractLayout { } public void addListener(SplitterClickListener listener) { - addListener(SPLITTER_CLICK_EVENT, SplitterClickEvent.class, listener, + addListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, + SplitterClickEvent.class, listener, SplitterClickListener.clickMethod); } public void removeListener(SplitterClickListener listener) { - removeListener(SPLITTER_CLICK_EVENT, SplitterClickEvent.class, listener); + removeListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, + SplitterClickEvent.class, listener); + } + + @Override + public AbstractSplitPanelState getState() { + return (AbstractSplitPanelState) super.getState(); } } diff --git a/src/com/vaadin/ui/AbstractTextField.java b/src/com/vaadin/ui/AbstractTextField.java index 346d370bd5..acb1d71ed8 100644 --- a/src/com/vaadin/ui/AbstractTextField.java +++ b/src/com/vaadin/ui/AbstractTextField.java @@ -18,10 +18,11 @@ import com.vaadin.event.FieldEvents.TextChangeListener; import com.vaadin.event.FieldEvents.TextChangeNotifier; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VTextField; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; -public abstract class AbstractTextField extends AbstractField implements - BlurNotifier, FocusNotifier, TextChangeNotifier { +public abstract class AbstractTextField extends AbstractField<String> implements + BlurNotifier, FocusNotifier, TextChangeNotifier, Vaadin6Component { /** * Value formatter used to format the string contents. @@ -99,9 +100,7 @@ public abstract class AbstractTextField extends AbstractField implements super(); } - @Override public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); if (getMaxLength() >= 0) { target.addAttribute("maxLength", getMaxLength()); @@ -173,8 +172,8 @@ public abstract class AbstractTextField extends AbstractField implements } @Override - public Object getValue() { - Object v = super.getValue(); + public String getValue() { + String v = super.getValue(); if (format == null || v == null) { return v; } @@ -185,12 +184,10 @@ public abstract class AbstractTextField extends AbstractField implements } } - @Override public void changeVariables(Object source, Map<String, Object> variables) { changingVariables = true; try { - super.changeVariables(source, variables); if (variables.containsKey(VTextField.VAR_CURSOR)) { Integer object = (Integer) variables.get(VTextField.VAR_CURSOR); @@ -252,7 +249,7 @@ public abstract class AbstractTextField extends AbstractField implements } @Override - public Class getType() { + public Class<String> getType() { return String.class; } @@ -375,7 +372,7 @@ public abstract class AbstractTextField extends AbstractField implements @Override protected boolean isEmpty() { - return super.isEmpty() || toString().length() == 0; + return super.isEmpty() || getValue().length() == 0; } /** @@ -463,7 +460,7 @@ public abstract class AbstractTextField extends AbstractField implements } @Override - protected void setInternalValue(Object newValue) { + protected void setInternalValue(String newValue) { if (changingVariables && !textChangeEventPending) { /* @@ -505,8 +502,7 @@ public abstract class AbstractTextField extends AbstractField implements } @Override - public void setValue(Object newValue) throws ReadOnlyException, - ConversionException { + public void setValue(Object newValue) throws ReadOnlyException { super.setValue(newValue); /* * Make sure w reset lastKnownTextContent field on value change. The @@ -515,7 +511,7 @@ public abstract class AbstractTextField extends AbstractField implements * case. AbstractField optimizes value change if the existing value is * reset. Also we need to force repaint if the flag is on. */ - if(lastKnownTextContent != null) { + if (lastKnownTextContent != null) { lastKnownTextContent = null; requestRepaint(); } @@ -753,4 +749,4 @@ public abstract class AbstractTextField extends AbstractField implements removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); } -}
\ No newline at end of file +} diff --git a/src/com/vaadin/ui/Accordion.java b/src/com/vaadin/ui/Accordion.java index 5cf805615c..b937c7bc2b 100644 --- a/src/com/vaadin/ui/Accordion.java +++ b/src/com/vaadin/ui/Accordion.java @@ -3,8 +3,6 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VAccordion; - /** * An accordion is a component similar to a {@link TabSheet}, but with a * vertical orientation and the selected component presented between tabs. @@ -16,8 +14,6 @@ import com.vaadin.terminal.gwt.client.ui.VAccordion; * * @see TabSheet */ -@SuppressWarnings("serial") -@ClientWidget(VAccordion.class) public class Accordion extends TabSheet { } diff --git a/src/com/vaadin/ui/AlignmentUtils.java b/src/com/vaadin/ui/AlignmentUtils.java deleted file mode 100644 index 029fc8c9b5..0000000000 --- a/src/com/vaadin/ui/AlignmentUtils.java +++ /dev/null @@ -1,151 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.ui; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import com.vaadin.ui.Layout.AlignmentHandler; - -/** - * Helper class for setting alignments using a short notation. - * - * Supported notation is: - * - * t,top for top alignment - * - * m,middle for vertical center alignment - * - * b,bottom for bottom alignment - * - * l,left for left alignment - * - * c,center for horizontal center alignment - * - * r,right for right alignment - * - * @deprecated {@code AlignmentUtils} has been replaced by {@link Alignment}. - */ -@SuppressWarnings({ "serial" }) -@Deprecated -public class AlignmentUtils implements Serializable { - - private static int horizontalMask = AlignmentHandler.ALIGNMENT_LEFT - | AlignmentHandler.ALIGNMENT_HORIZONTAL_CENTER - | AlignmentHandler.ALIGNMENT_RIGHT; - - private static int verticalMask = AlignmentHandler.ALIGNMENT_TOP - | AlignmentHandler.ALIGNMENT_VERTICAL_CENTER - | AlignmentHandler.ALIGNMENT_BOTTOM; - - private static Map<String, Integer> alignmentStrings = new HashMap<String, Integer>(); - - private static void addMapping(int alignment, String... values) { - for (String s : values) { - alignmentStrings.put(s, alignment); - } - } - - static { - addMapping(AlignmentHandler.ALIGNMENT_TOP, "t", "top"); - addMapping(AlignmentHandler.ALIGNMENT_BOTTOM, "b", "bottom"); - addMapping(AlignmentHandler.ALIGNMENT_VERTICAL_CENTER, "m", "middle"); - - addMapping(AlignmentHandler.ALIGNMENT_LEFT, "l", "left"); - addMapping(AlignmentHandler.ALIGNMENT_RIGHT, "r", "right"); - addMapping(AlignmentHandler.ALIGNMENT_HORIZONTAL_CENTER, "c", "center"); - } - - /** - * Set the alignment for the component using short notation - * - * @param parent - * @param component - * @param alignment - * String containing one or two alignment strings. If short - * notation "r","t",etc is used valid strings include - * "r","rt","tr","t". If the longer notation is used the - * alignments should be separated by a space e.g. - * "right","right top","top right","top". It is valid to mix - * short and long notation but they must be separated by a space - * e.g. "r top". - * @throws IllegalArgumentException - */ - public static void setComponentAlignment(AlignmentHandler parent, - Component component, String alignment) - throws IllegalArgumentException { - if (alignment == null || alignment.length() == 0) { - throw new IllegalArgumentException( - "alignment for setComponentAlignment() cannot be null or empty"); - } - - Integer currentAlignment = parent.getComponentAlignment(component) - .getBitMask(); - - if (alignment.length() == 1) { - // Use short form "t","l",... - currentAlignment = parseAlignment(alignment.substring(0, 1), - currentAlignment); - } else if (alignment.length() == 2) { - // Use short form "tr","lb",... - currentAlignment = parseAlignment(alignment.substring(0, 1), - currentAlignment); - currentAlignment = parseAlignment(alignment.substring(1, 2), - currentAlignment); - } else { - // Alignments are separated by space - String[] strings = alignment.split(" "); - if (strings.length > 2) { - throw new IllegalArgumentException( - "alignment for setComponentAlignment() should not contain more than 2 alignments"); - } - for (String alignmentString : strings) { - currentAlignment = parseAlignment(alignmentString, - currentAlignment); - } - } - - int horizontalAlignment = currentAlignment & horizontalMask; - int verticalAlignment = currentAlignment & verticalMask; - parent.setComponentAlignment(component, new Alignment( - horizontalAlignment + verticalAlignment)); - } - - /** - * Parse alignmentString which contains one alignment (horizontal or - * vertical) and return and updated version of the passed alignment where - * the alignment in one direction has been changed. If the passed - * alignmentString is unknown an exception is thrown - * - * @param alignmentString - * @param alignment - * @return - * @throws IllegalArgumentException - */ - private static int parseAlignment(String alignmentString, int alignment) - throws IllegalArgumentException { - Integer parsed = alignmentStrings.get(alignmentString.toLowerCase()); - - if (parsed == null) { - throw new IllegalArgumentException( - "Could not parse alignment string '" + alignmentString - + "'"); - } - - if ((parsed & horizontalMask) != 0) { - // Get the vertical alignment from the current alignment - int vertical = (alignment & verticalMask); - // Add the parsed horizontal alignment - alignment = (vertical | parsed); - } else { - // Get the horizontal alignment from the current alignment - int horizontal = (alignment & horizontalMask); - // Add the parsed vertical alignment - alignment = (horizontal | parsed); - } - - return alignment; - } -} diff --git a/src/com/vaadin/ui/Audio.java b/src/com/vaadin/ui/Audio.java index 574c1f4186..ac2ee869a6 100644 --- a/src/com/vaadin/ui/Audio.java +++ b/src/com/vaadin/ui/Audio.java @@ -5,7 +5,6 @@ package com.vaadin.ui; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VAudio; /** * The Audio component translates into an HTML5 <audio> element and as @@ -28,7 +27,6 @@ import com.vaadin.terminal.gwt.client.ui.VAudio; * @author Vaadin Ltd * @since 6.7.0 */ -@ClientWidget(VAudio.class) public class Audio extends AbstractMedia { public Audio() { diff --git a/src/com/vaadin/ui/BaseFieldFactory.java b/src/com/vaadin/ui/BaseFieldFactory.java deleted file mode 100644 index fe271aabe4..0000000000 --- a/src/com/vaadin/ui/BaseFieldFactory.java +++ /dev/null @@ -1,90 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.ui; - -import com.vaadin.data.Container; -import com.vaadin.data.Item; -import com.vaadin.data.Property; - -/** - * Default implementation of the the following Field types are used by default: - * <p> - * <b>Boolean</b>: Button(switchMode:true).<br/> - * <b>Date</b>: DateField(resolution: day).<br/> - * <b>Item</b>: Form. <br/> - * <b>default field type</b>: TextField. - * <p> - * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 3.1 - * @deprecated use {@link DefaultFieldFactory} or own implementations on - * {@link FormFieldFactory} or {@link TableFieldFactory} instead. - */ - -@Deprecated -@SuppressWarnings("serial") -public class BaseFieldFactory implements FieldFactory { - - /** - * Creates the field based on type of data. - * - * - * @param type - * the type of data presented in field. - * @param uiContext - * the context where the Field is presented. - * - * @see com.vaadin.ui.FieldFactory#createField(Class, Component) - */ - public Field createField(Class<?> type, Component uiContext) { - return DefaultFieldFactory.createFieldByPropertyType(type); - } - - /** - * Creates the field based on the datasource property. - * - * @see com.vaadin.ui.FieldFactory#createField(Property, Component) - */ - public Field createField(Property property, Component uiContext) { - if (property != null) { - return createField(property.getType(), uiContext); - } else { - return null; - } - } - - /** - * Creates the field based on the item and property id. - * - * @see com.vaadin.ui.FieldFactory#createField(Item, Object, Component) - */ - public Field createField(Item item, Object propertyId, Component uiContext) { - if (item != null && propertyId != null) { - final Field f = createField(item.getItemProperty(propertyId), - uiContext); - if (f instanceof AbstractComponent) { - String name = DefaultFieldFactory - .createCaptionByPropertyId(propertyId); - f.setCaption(name); - } - return f; - } else { - return null; - } - } - - /** - * @see com.vaadin.ui.FieldFactory#createField(com.vaadin.data.Container, - * java.lang.Object, java.lang.Object, com.vaadin.ui.Component) - */ - public Field createField(Container container, Object itemId, - Object propertyId, Component uiContext) { - return createField(container.getContainerProperty(itemId, propertyId), - uiContext); - } - -} diff --git a/src/com/vaadin/ui/Button.java b/src/com/vaadin/ui/Button.java index 3c99784592..f5e45ef3ef 100644 --- a/src/com/vaadin/ui/Button.java +++ b/src/com/vaadin/ui/Button.java @@ -4,27 +4,25 @@ package com.vaadin.ui; -import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; -import java.util.Map; -import com.vaadin.data.Property; +import com.vaadin.event.Action; import com.vaadin.event.FieldEvents; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; 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.PaintException; -import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VButton; -import com.vaadin.ui.ClientWidget.LoadStyle; -import com.vaadin.ui.themes.BaseTheme; +import com.vaadin.terminal.gwt.client.ui.button.ButtonServerRpc; +import com.vaadin.terminal.gwt.client.ui.button.ButtonState; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.Component.Focusable; /** * A generic button component. @@ -35,29 +33,39 @@ import com.vaadin.ui.themes.BaseTheme; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VButton.class, loadStyle = LoadStyle.EAGER) -public class Button extends AbstractField implements FieldEvents.BlurNotifier, - FieldEvents.FocusNotifier { +public class Button extends AbstractComponent implements + FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Focusable, + Action.ShortcutNotifier { - /* Private members */ + private ButtonServerRpc rpc = new ButtonServerRpc() { + public void click(MouseEventDetails mouseEventDetails) { + fireClick(mouseEventDetails); + } - boolean switchMode = false; + public void disableOnClick() { + // Could be optimized so the button is not repainted because of + // this (client side has already disabled the button) + setEnabled(false); + } + }; - boolean disableOnClick = false; + FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) { + @Override + protected void fireEvent(Event event) { + Button.this.fireEvent(event); + } + }; /** - * Creates a new push button. The value of the push button is false and it - * is immediate by default. - * + * Creates a new push button. */ public Button() { - setValue(Boolean.FALSE); + registerRpc(rpc); + registerRpc(focusBlurRpc); } /** - * Creates a new push button. - * - * The value of the push button is false and it is immediate by default. + * Creates a new push button with the given caption. * * @param caption * the Button caption. @@ -68,7 +76,7 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, } /** - * Creates a new push button with click listener. + * Creates a new push button with a click listener. * * @param caption * the Button caption. @@ -81,239 +89,6 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, } /** - * Creates a new push button with a method listening button clicks. Using - * this method is discouraged because it cannot be checked during - * compilation. Use - * {@link #Button(String, com.vaadin.ui.Button.ClickListener)} instead. The - * method must have either no parameters, or only one parameter of - * Button.ClickEvent type. - * - * @param caption - * the Button caption. - * @param target - * the Object having the method for listening button clicks. - * @param methodName - * the name of the method in target object, that receives button - * click events. - */ - public Button(String caption, Object target, String methodName) { - this(caption); - addListener(ClickEvent.class, target, methodName); - } - - /** - * Creates a new switch button with initial value. - * - * @param state - * the Initial state of the switch-button. - * @param initialState - * @deprecated use {@link CheckBox} instead of Button in "switchmode" - */ - @Deprecated - public Button(String caption, boolean initialState) { - setCaption(caption); - setValue(Boolean.valueOf(initialState)); - setSwitchMode(true); - } - - /** - * Creates a new switch button that is connected to a boolean property. - * - * @param state - * the Initial state of the switch-button. - * @param dataSource - * @deprecated use {@link CheckBox} instead of Button in "switchmode" - */ - @Deprecated - public Button(String caption, Property dataSource) { - setCaption(caption); - setSwitchMode(true); - setPropertyDataSource(dataSource); - } - - /** - * Paints the content of this component. - * - * @param event - * the PaintEvent. - * @throws IOException - * if the writing failed due to input/output error. - * @throws PaintException - * if the paint operation failed. - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - - if (isSwitchMode()) { - target.addAttribute("type", "switch"); - } - target.addVariable(this, "state", booleanValue()); - - if (isDisableOnClick()) { - target.addAttribute(VButton.ATTR_DISABLE_ON_CLICK, true); - } - if (clickShortcut != null) { - target.addAttribute("keycode", clickShortcut.getKeyCode()); - } - } - - /** - * Invoked when the value of a variable has changed. Button listeners are - * notified if the button is clicked. - * - * @param source - * @param variables - */ - @Override - public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); - - if (variables.containsKey("disabledOnClick")) { - // Could be optimized so the button is not repainted because of this - // (client side has already disabled the button) - setEnabled(false); - } - - if (!isReadOnly() && variables.containsKey("state")) { - // Gets the new and old button states - final Boolean newValue = (Boolean) variables.get("state"); - final Boolean oldValue = (Boolean) getValue(); - - if (isSwitchMode()) { - - // For switch button, the event is only sent if the - // switch state is changed - if (newValue != null && !newValue.equals(oldValue) - && !isReadOnly()) { - setValue(newValue); - if (variables.containsKey("mousedetails")) { - fireClick(MouseEventDetails - .deSerialize((String) variables - .get("mousedetails"))); - } else { - // for compatibility with custom implementations which - // don't send mouse details - fireClick(); - } - } - } else { - - // Only send click event if the button is pushed - if (newValue.booleanValue()) { - if (variables.containsKey("mousedetails")) { - fireClick(MouseEventDetails - .deSerialize((String) variables - .get("mousedetails"))); - } else { - // for compatibility with custom implementations which - // don't send mouse details - fireClick(); - } - } - - // If the button is true for some reason, release it - if (null == oldValue || oldValue.booleanValue()) { - setValue(Boolean.FALSE); - } - } - } - - if (variables.containsKey(FocusEvent.EVENT_ID)) { - fireEvent(new FocusEvent(this)); - } - if (variables.containsKey(BlurEvent.EVENT_ID)) { - fireEvent(new BlurEvent(this)); - } - } - - /** - * Checks if it is switchMode. - * - * @return <code>true</code> if it is in Switch Mode, otherwise - * <code>false</code>. - * @deprecated the {@link CheckBox} component should be used instead of - * Button in switch mode - */ - @Deprecated - public boolean isSwitchMode() { - return switchMode; - } - - /** - * Sets the switchMode. - * - * @param switchMode - * The switchMode to set. - * @deprecated the {@link CheckBox} component should be used instead of - * Button in switch mode - */ - @Deprecated - public void setSwitchMode(boolean switchMode) { - this.switchMode = switchMode; - if (!switchMode) { - setImmediate(true); - if (booleanValue()) { - setValue(Boolean.FALSE); - } - } - } - - /** - * Get the boolean value of the button state. - * - * @return True iff the button is pressed down or checked. - */ - public boolean booleanValue() { - Boolean value = (Boolean) getValue(); - return (null == value) ? false : value.booleanValue(); - } - - /** - * Sets immediate mode. Push buttons can not be set in non-immediate mode. - * - * @see com.vaadin.ui.AbstractComponent#setImmediate(boolean) - */ - @Override - public void setImmediate(boolean immediate) { - // Push buttons are always immediate - super.setImmediate(!isSwitchMode() || immediate); - } - - /** - * The type of the button as a property. - * - * @see com.vaadin.data.Property#getType() - */ - @Override - public Class getType() { - return Boolean.class; - } - - /* Click event */ - - private static final Method BUTTON_CLICK_METHOD; - - /** - * Button style with no decorations. Looks like a link, acts like a button - * - * @deprecated use {@link BaseTheme#BUTTON_LINK} instead. - */ - @Deprecated - public static final String STYLE_LINK = "link"; - - static { - try { - BUTTON_CLICK_METHOD = ClickListener.class.getDeclaredMethod( - "buttonClick", new Class[] { ClickEvent.class }); - } catch (final java.lang.NoSuchMethodException e) { - // This should never happen - throw new java.lang.RuntimeException( - "Internal error finding methods in Button"); - } - } - - /** * Click event. This event is thrown, when the button is clicked. * * @author Vaadin Ltd. @@ -484,6 +259,10 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, */ public interface ClickListener extends Serializable { + public static final Method BUTTON_CLICK_METHOD = ReflectTools + .findMethod(ClickListener.class, "buttonClick", + ClickEvent.class); + /** * Called when a {@link Button} has been clicked. A reference to the * button is given by {@link ClickEvent#getButton()}. @@ -502,7 +281,8 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, * the Listener to be added. */ public void addListener(ClickListener listener) { - addListener(ClickEvent.class, listener, BUTTON_CLICK_METHOD); + addListener(ClickEvent.class, listener, + ClickListener.BUTTON_CLICK_METHOD); } /** @@ -512,7 +292,8 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, * the Listener to be removed. */ public void removeListener(ClickListener listener) { - removeListener(ClickEvent.class, listener, BUTTON_CLICK_METHOD); + removeListener(ClickEvent.class, listener, + ClickListener.BUTTON_CLICK_METHOD); } /** @@ -549,16 +330,6 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, fireEvent(new Button.ClickEvent(this, details)); } - @Override - protected void setInternalValue(Object newValue) { - // Make sure only booleans get through - if (null != newValue && !(newValue instanceof Boolean)) { - throw new IllegalArgumentException(getClass().getSimpleName() - + " only accepts Boolean values"); - } - super.setInternalValue(newValue); - } - public void addListener(BlurListener listener) { addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener, BlurListener.blurMethod); @@ -584,6 +355,8 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, protected ClickShortcut clickShortcut; + private int tabIndex = 0; + /** * Makes it possible to invoke a click on this button by pressing the given * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/> @@ -601,6 +374,7 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, } clickShortcut = new ClickShortcut(this, keyCode, modifiers); addShortcutListener(clickShortcut); + getState().setClickShortcutKeyCode(clickShortcut.getKeyCode()); } /** @@ -611,6 +385,7 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, if (clickShortcut != null) { removeShortcutListener(clickShortcut); clickShortcut = null; + getState().setClickShortcutKeyCode(0); } } @@ -678,7 +453,7 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, * @return true if the button is disabled when clicked, false otherwise */ public boolean isDisableOnClick() { - return disableOnClick; + return getState().isDisableOnClick(); } /** @@ -690,8 +465,28 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, * true to disable button when it is clicked, false otherwise */ public void setDisableOnClick(boolean disableOnClick) { - this.disableOnClick = disableOnClick; + getState().setDisableOnClick(disableOnClick); requestRepaint(); } + public int getTabIndex() { + return tabIndex; + } + + public void setTabIndex(int tabIndex) { + this.tabIndex = tabIndex; + + } + + @Override + public void focus() { + // Overridden only to make public + super.focus(); + } + + @Override + public ButtonState getState() { + return (ButtonState) super.getState(); + } + } diff --git a/src/com/vaadin/ui/CheckBox.java b/src/com/vaadin/ui/CheckBox.java index 00a248cdf3..147a270059 100644 --- a/src/com/vaadin/ui/CheckBox.java +++ b/src/com/vaadin/ui/CheckBox.java @@ -4,110 +4,137 @@ package com.vaadin.ui; -import java.lang.reflect.Method; - import com.vaadin.data.Property; +import com.vaadin.event.FieldEvents.BlurEvent; +import com.vaadin.event.FieldEvents.BlurListener; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusEvent; +import com.vaadin.event.FieldEvents.FocusListener; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.checkbox.CheckBoxServerRpc; +import com.vaadin.terminal.gwt.client.ui.checkbox.CheckBoxState; -@ClientWidget(com.vaadin.terminal.gwt.client.ui.VCheckBox.class) -public class CheckBox extends Button { - /** - * Creates a new switch button. - */ - public CheckBox() { - setSwitchMode(true); - } +public class CheckBox extends AbstractField<Boolean> { + + private CheckBoxServerRpc rpc = new CheckBoxServerRpc() { + + public void setChecked(boolean checked, + MouseEventDetails mouseEventDetails) { + if (isReadOnly()) { + return; + } + + final Boolean oldValue = getValue(); + final Boolean newValue = checked; + + if (!newValue.equals(oldValue)) { + // The event is only sent if the switch state is changed + setValue(newValue); + } + + } + }; + + FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) { + @Override + protected void fireEvent(Event event) { + CheckBox.this.fireEvent(event); + } + }; /** - * Creates a new switch button with a caption and a set initial state. - * - * @param caption - * the caption of the switch button - * @param initialState - * the initial state of the switch button + * Creates a new checkbox. */ - @SuppressWarnings("deprecation") - public CheckBox(String caption, boolean initialState) { - super(caption, initialState); + public CheckBox() { + registerRpc(rpc); + registerRpc(focusBlurRpc); + setValue(Boolean.FALSE); } /** - * Creates a new switch button with a caption and a click listener. + * Creates a new checkbox with a set caption. * * @param caption - * the caption of the switch button - * @param listener - * the click listener + * the Checkbox caption. */ - public CheckBox(String caption, ClickListener listener) { - super(caption, listener); - setSwitchMode(true); + public CheckBox(String caption) { + this(); + setCaption(caption); } /** - * Convenience method for creating a new switch button with a method - * listening button clicks. Using this method is discouraged because it - * cannot be checked during compilation. Use - * {@link #addListener(Class, Object, Method)} or - * {@link #addListener(com.vaadin.ui.Component.Listener)} instead. The - * method must have either no parameters, or only one parameter of - * Button.ClickEvent type. + * Creates a new checkbox with a caption and a set initial state. * * @param caption - * the Button caption. - * @param target - * the Object having the method for listening button clicks. - * @param methodName - * the name of the method in target object, that receives button - * click events. + * the caption of the checkbox + * @param initialState + * the initial state of the checkbox */ - public CheckBox(String caption, Object target, String methodName) { - super(caption, target, methodName); - setSwitchMode(true); + public CheckBox(String caption, boolean initialState) { + this(caption); + setValue(initialState); } /** - * Creates a new switch button that is connected to a boolean property. + * Creates a new checkbox that is connected to a boolean property. * * @param state * the Initial state of the switch-button. * @param dataSource */ - @SuppressWarnings("deprecation") - public CheckBox(String caption, Property dataSource) { - super(caption, dataSource); - setSwitchMode(true); + public CheckBox(String caption, Property<?> dataSource) { + this(caption); + setPropertyDataSource(dataSource); } - /** - * Creates a new push button with a set caption. - * - * The value of the push button is always false and they are immediate by - * default. - * - * @param caption - * the Button caption. - */ + @Override + public Class<Boolean> getType() { + return Boolean.class; + } - @SuppressWarnings("deprecation") - public CheckBox(String caption) { - super(caption, false); + @Override + public CheckBoxState getState() { + return (CheckBoxState) super.getState(); } - @Deprecated @Override - public void setSwitchMode(boolean switchMode) - throws UnsupportedOperationException { - if (this.switchMode && !switchMode) { - throw new UnsupportedOperationException( - "CheckBox is always in switch mode (consider using a Button)"); + protected void setInternalValue(Boolean newValue) { + super.setInternalValue(newValue); + if (newValue == null) { + newValue = false; } - super.setSwitchMode(true); + getState().setChecked(newValue); } - @Override - public void setDisableOnClick(boolean disableOnClick) { - throw new UnsupportedOperationException( - "CheckBox does not support disable on click"); + 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); + } + + 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); + } + + /** + * Get the boolean value of the button state. + * + * @return True iff the button is pressed down or checked. + * + * @deprecated Use {@link #getValue()} instead and, if needed, handle null + * values. + */ + @Deprecated + public boolean booleanValue() { + Boolean value = getValue(); + return (null == value) ? false : value.booleanValue(); + } } diff --git a/src/com/vaadin/ui/ComboBox.java b/src/com/vaadin/ui/ComboBox.java index bc7ab6f994..6286dad124 100644 --- a/src/com/vaadin/ui/ComboBox.java +++ b/src/com/vaadin/ui/ComboBox.java @@ -9,7 +9,7 @@ import java.util.Collection; import com.vaadin.data.Container; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VFilterSelect; +import com.vaadin.terminal.gwt.client.ui.combobox.VFilterSelect; /** * A filtering dropdown single-select. Suitable for newItemsAllowed, but it's @@ -20,7 +20,6 @@ import com.vaadin.terminal.gwt.client.ui.VFilterSelect; * */ @SuppressWarnings("serial") -@ClientWidget(VFilterSelect.class) public class ComboBox extends Select { private String inputPrompt = null; @@ -33,36 +32,24 @@ public class ComboBox extends Select { private boolean textInputAllowed = true; public ComboBox() { - setMultiSelect(false); setNewItemsAllowed(false); } public ComboBox(String caption, Collection<?> options) { super(caption, options); - setMultiSelect(false); setNewItemsAllowed(false); } public ComboBox(String caption, Container dataSource) { super(caption, dataSource); - setMultiSelect(false); setNewItemsAllowed(false); } public ComboBox(String caption) { super(caption); - setMultiSelect(false); setNewItemsAllowed(false); } - @Override - public void setMultiSelect(boolean multiSelect) { - if (multiSelect && !isMultiSelect()) { - throw new UnsupportedOperationException("Multiselect not supported"); - } - super.setMultiSelect(multiSelect); - } - /** * Gets the current input prompt. * diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java index b32aad2fca..3632c4ca5e 100644 --- a/src/com/vaadin/ui/Component.java +++ b/src/com/vaadin/ui/Component.java @@ -5,7 +5,6 @@ package com.vaadin.ui; import java.io.Serializable; -import java.util.Collection; import java.util.EventListener; import java.util.EventObject; import java.util.Locale; @@ -13,10 +12,11 @@ import java.util.Locale; import com.vaadin.Application; import com.vaadin.event.FieldEvents; import com.vaadin.terminal.ErrorMessage; -import com.vaadin.terminal.Paintable; import com.vaadin.terminal.Resource; import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.server.ClientConnector; /** * {@code Component} is the top-level interface that is and must be implemented @@ -51,8 +51,7 @@ import com.vaadin.terminal.VariableOwner; * @VERSION@ * @since 3.0 */ -public interface Component extends Paintable, VariableOwner, Sizeable, - Serializable { +public interface Component extends ClientConnector, Sizeable, Serializable { /** * Gets all user-defined CSS style names of a component. If the component @@ -115,9 +114,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * </p> * * <p> - * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * This method will trigger a {@link RepaintRequestEvent}. * </p> * * @param style @@ -159,9 +156,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * </pre> * * <p> - * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * This method will trigger a {@link RepaintRequestEvent}. * </p> * * @param style @@ -183,9 +178,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * style names defined in Vaadin or GWT can not be removed. * </p> * - * * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * * This method will trigger a {@link RepaintRequestEvent}. * * @param style * the style name or style names to be removed @@ -202,8 +195,14 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * component are also disabled. Components are enabled by default. * * <p> - * As a security feature, all variable change events for disabled components - * are blocked on the server-side. + * As a security feature, all updates for disabled components are blocked on + * the server-side. + * </p> + * + * <p> + * Note that this method only returns the status of the component and does + * not take parents into account. Even though this method returns true the + * component can be disabled to the user if a parent is disabled. * </p> * * @return <code>true</code> if the component and its parent are enabled, @@ -216,9 +215,6 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * Enables or disables the component. The user can not interact disabled * components, which are shown with a style that indicates the status, * usually shaded in light gray color. Components are enabled by default. - * Children of a disabled component are automatically disabled; if a child - * component is explicitly set as disabled, changes in the disabled status - * of its parents do not change its status. * * <pre> * Button enabled = new Button("Enabled"); @@ -231,10 +227,9 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * </pre> * * <p> - * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent} for the component and, if it is a - * {@link ComponentContainer}, for all its children recursively. + * This method will trigger a {@link RepaintRequestEvent} for the component + * and, if it is a {@link ComponentContainer}, for all its children + * recursively. * </p> * * @param enabled @@ -248,27 +243,22 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * * <p> * Visible components are drawn in the user interface, while invisible ones - * are not. The effect is not merely a cosmetic CSS change, but the entire - * HTML element will be empty. Making a component invisible through this - * property can alter the positioning of other components. + * are not. The effect is not merely a cosmetic CSS change - no information + * about an invisible component will be sent to the client. The effect is + * thus the same as removing the component from its parent. Making a + * component invisible through this property can alter the positioning of + * other components. * </p> * * <p> - * A component is visible only if all its parents are also visible. Notice - * that if a child component is explicitly set as invisible, changes in the - * visibility status of its parents do not change its status. + * A component is visible only if all its parents are also visible. This is + * not checked by this method though, so even if this method returns true, + * the component can be hidden from the user because a parent is set to + * invisible. * </p> * - * <p> - * This method does not check whether the component is attached (see - * {@link #attach()}). The component and all its parents may be considered - * "visible", but not necessarily attached to application. To test if - * component will actually be drawn, check both its visibility and that - * {@link #getApplication()} does not return {@code null}. - * </p> - * - * @return <code>true</code> if the component is visible in the user - * interface, <code>false</code> if not + * @return <code>true</code> if the component has been set to be visible in + * the user interface, <code>false</code> if not * @see #setVisible(boolean) * @see #attach() */ @@ -279,8 +269,9 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * * <p> * Visible components are drawn in the user interface, while invisible ones - * are not. The effect is not merely a cosmetic CSS change, but the entire - * HTML element will be empty. + * are not. The effect is not merely a cosmetic CSS change - no information + * about an invisible component will be sent to the client. The effect is + * thus the same as removing the component from its parent. * </p> * * <pre> @@ -315,7 +306,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * @return the parent component * @see #setParent(Component) */ - public Component getParent(); + public HasComponents getParent(); /** * Sets the parent component of the component. @@ -327,7 +318,6 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * is attached to the application, {@link #detach()} is called for the * component. * </p> - * * <p> * This method is rarely called directly. The * {@link ComponentContainer#addComponent(Component)} method is normally @@ -346,7 +336,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * if a parent is given even though the component already has a * parent */ - public void setParent(Component parent); + public void setParent(HasComponents parent); /** * Tests whether the component is in the read-only mode. The user can not @@ -389,9 +379,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * </p> * * <p> - * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * This method will trigger a {@link RepaintRequestEvent}. * </p> * * @param readOnly @@ -458,10 +446,8 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * </p> * * <p> - * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. A reimplementation should call the superclass - * implementation. + * This method will trigger a {@link RepaintRequestEvent}. A + * reimplementation should call the superclass implementation. * </p> * * @param caption @@ -531,9 +517,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * {@code v-caption} . * </p> * - * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * This method will trigger a {@link RepaintRequestEvent}. * * @param icon * the icon of the component. If null, no icon is shown and it @@ -560,7 +544,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * @return the parent window of the component or <code>null</code> if it is * not attached to a window or is itself a window */ - public Window getWindow(); + public Root getRoot(); /** * Gets the application object to which the component is attached. @@ -593,7 +577,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * <p> * Reimplementing the {@code attach()} method is useful for tasks that need * to get a reference to the parent, window, or application object with the - * {@link #getParent()}, {@link #getWindow()}, and {@link #getApplication()} + * {@link #getParent()}, {@link #getRoot()}, and {@link #getApplication()} * methods. A component does not yet know these objects in the constructor, * so in such case, the methods will return {@code null}. For example, the * following is invalid: @@ -618,6 +602,11 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * application, the {@code attach()} is called immediately from * {@link #setParent(Component)}. * </p> + * <p> + * This method must call {@link Root#componentAttached(Component)} to let + * the Root know that a new Component has been attached. + * </p> + * * * <pre> * public class AttachExample extends CustomComponent { @@ -648,11 +637,16 @@ public interface Component extends Paintable, VariableOwner, Sizeable, * Notifies the component that it is detached from the application. * * <p> - * The {@link #getApplication()} and {@link #getWindow()} methods might - * return <code>null</code> after this method is called. + * The {@link #getApplication()} and {@link #getRoot()} methods might return + * <code>null</code> after this method is called. * </p> * * <p> + * This method must call {@link Root#componentDetached(Component)} to let + * the Root know that a new Component has been attached. + * </p> + * * + * <p> * The caller of this method is {@link #setParent(Component)} if the parent * is in the application. When the parent is detached from the application * it is its response to call {@link #detach()} for all the children and to @@ -685,31 +679,114 @@ public interface Component extends Paintable, VariableOwner, Sizeable, public Locale getLocale(); /** - * The child components of the component must call this method when they - * need repainting. The call must be made even in the case in which the - * children sent the repaint request themselves. + * Returns the current shared state bean for the component. The state (or + * changes to it) is communicated from the server to the client. * - * <p> - * A repaint request is ignored if the component is invisible. - * </p> + * Subclasses can use a more specific return type for this method. + * + * @return The state object for the component * + * @since 7.0 + */ + public ComponentState getState(); + + /** + * Called before the shared state is sent to the client. Gives the component + * an opportunity to set computed/dynamic state values e.g. state values + * that depend on other component features. * <p> - * This method is called automatically by {@link AbstractComponent}, which - * also provides a default implementation of it. As this is a somewhat - * internal feature, it is rarely necessary to reimplement this or call it - * explicitly. + * This method must not alter the component hierarchy in any way. * </p> * - * @param alreadyNotified - * the collection of repaint request listeners that have been - * already notified by the child. This component should not - * re-notify the listed listeners again. The container given as - * parameter must be modifiable as the component might modify it - * and pass it forward. A {@code null} parameter is interpreted - * as an empty collection. + * @since 7.0 + */ + public void updateState(); + + /** + * Adds an unique id for component that get's transferred to terminal for + * testing purposes. Keeping identifiers unique is the responsibility of the + * programmer. + * + * @param id + * An alphanumeric id + */ + public void setDebugId(String id); + + /** + * Get's currently set debug identifier + * + * @return current debug id, null if not set */ - public void childRequestedRepaint( - Collection<RepaintRequestListener> alreadyNotified); + public String getDebugId(); + + /** + * Requests that the component should be repainted as soon as possible. + */ + public void requestRepaint(); + + /** + * Repaint request event is thrown when the connector needs to be repainted. + * This is typically done when the <code>paint</code> method would return + * dissimilar UIDL from the previous call of the method. + */ + @SuppressWarnings("serial") + public static class RepaintRequestEvent extends EventObject { + + /** + * Constructs a new event. + * + * @param source + * the paintable needing repaint. + */ + public RepaintRequestEvent(ClientConnector source) { + super(source); + } + + /** + * Gets the connector needing repainting. + * + * @return Paintable for which the <code>paint</code> method will return + * dissimilar UIDL from the previous call of the method. + */ + public ClientConnector getConnector() { + return (ClientConnector) getSource(); + } + } + + /** + * Listens repaint requests. The <code>repaintRequested</code> method is + * called when the paintable needs to be repainted. This is typically done + * when the <code>paint</code> method would return dissimilar UIDL from the + * previous call of the method. + */ + public interface RepaintRequestListener extends Serializable { + + /** + * Receives repaint request events. + * + * @param event + * the repaint request event specifying the paintable source. + */ + public void repaintRequested(RepaintRequestEvent event); + } + + /** + * Adds repaint request listener. In order to assure that no repaint + * requests are missed, the new repaint listener should paint the paintable + * right after adding itself as listener. + * + * @param listener + * the listener to be added. + */ + public void addListener(RepaintRequestListener listener); + + /** + * Removes repaint request listener. + * + * @param listener + * the listener to be removed. + */ + public void removeListener(RepaintRequestListener listener); /* Component event framework */ @@ -1099,4 +1176,5 @@ public interface Component extends Paintable, VariableOwner, Sizeable, public void setTabIndex(int tabIndex); } + } diff --git a/src/com/vaadin/ui/ComponentContainer.java b/src/com/vaadin/ui/ComponentContainer.java index 1e1f0796ca..8182d54b56 100644 --- a/src/com/vaadin/ui/ComponentContainer.java +++ b/src/com/vaadin/ui/ComponentContainer.java @@ -5,7 +5,6 @@ package com.vaadin.ui; import java.io.Serializable; -import java.util.Iterator; /** * Extension to the {@link Component} interface which adds to it the capacity to @@ -17,7 +16,7 @@ import java.util.Iterator; * @VERSION@ * @since 3.0 */ -public interface ComponentContainer extends Component { +public interface ComponentContainer extends HasComponents { /** * Adds the component into this container. @@ -61,22 +60,13 @@ public interface ComponentContainer extends Component { public void replaceComponent(Component oldComponent, Component newComponent); /** - * Gets an iterator to the collection of contained components. Using this - * iterator it is possible to step through all components contained in this - * container. + * Gets the number of children this {@link ComponentContainer} has. This + * must be symmetric with what {@link #getComponentIterator()} returns. * - * @return the component iterator. + * @return The number of child components this container has. + * @since 7.0.0 */ - public Iterator<Component> getComponentIterator(); - - /** - * Causes a repaint of this component, and all components below it. - * - * This should only be used in special cases, e.g when the state of a - * descendant depends on the state of a ancestor. - * - */ - public void requestRepaintAll(); + public int getComponentCount(); /** * Moves all components from an another container into this container. The diff --git a/src/com/vaadin/ui/CssLayout.java b/src/com/vaadin/ui/CssLayout.java index b9432df6b6..0a2656af31 100644 --- a/src/com/vaadin/ui/CssLayout.java +++ b/src/com/vaadin/ui/CssLayout.java @@ -3,18 +3,17 @@ */ package com.vaadin.ui; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import com.vaadin.event.LayoutEvents.LayoutClickEvent; import com.vaadin.event.LayoutEvents.LayoutClickListener; import com.vaadin.event.LayoutEvents.LayoutClickNotifier; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.Paintable; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.ui.VCssLayout; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.csslayout.CssLayoutServerRpc; +import com.vaadin.terminal.gwt.client.ui.csslayout.CssLayoutState; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; /** * CssLayout is a layout component that can be used in browser environment only. @@ -57,16 +56,25 @@ import com.vaadin.terminal.gwt.client.ui.VCssLayout; * @since 6.1 brought in from "FastLayouts" incubator project * */ -@ClientWidget(VCssLayout.class) public class CssLayout extends AbstractLayout implements LayoutClickNotifier { - private static final String CLICK_EVENT = EventId.LAYOUT_CLICK; + private CssLayoutServerRpc rpc = new CssLayoutServerRpc() { + public void layoutClick(MouseEventDetails mouseDetails, + Connector clickedConnector) { + fireEvent(LayoutClickEvent.createEvent(CssLayout.this, + mouseDetails, clickedConnector)); + } + }; /** * Custom layout slots containing the components. */ protected LinkedList<Component> components = new LinkedList<Component>(); + public CssLayout() { + registerRpc(rpc); + } + /** * Add a component into this container. The component is added to the right * or under the previous component. @@ -173,33 +181,22 @@ public class CssLayout extends AbstractLayout implements LayoutClickNotifier { return components.size(); } - /** - * Paints the content of this component. - * - * @param target - * the Paint Event. - * @throws PaintException - * if the paint operation failed. - */ @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - HashMap<Paintable, String> componentCss = null; - // Adds all items in all the locations - for (Component c : components) { - // Paint child component UIDL - c.paint(target); - String componentCssString = getCss(c); + public void updateState() { + super.updateState(); + getState().getChildCss().clear(); + for (Component child : this) { + String componentCssString = getCss(child); if (componentCssString != null) { - if (componentCss == null) { - componentCss = new HashMap<Paintable, String>(); - } - componentCss.put(c, componentCssString); + getState().getChildCss().put(child, componentCssString); } + } - if (componentCss != null) { - target.addAttribute("css", componentCss); - } + } + + @Override + public CssLayoutState getState() { + return (CssLayoutState) super.getState(); } /** @@ -267,12 +264,14 @@ public class CssLayout extends AbstractLayout implements LayoutClickNotifier { } public void addListener(LayoutClickListener listener) { - addListener(CLICK_EVENT, LayoutClickEvent.class, listener, + addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener, LayoutClickListener.clickMethod); } public void removeListener(LayoutClickListener listener) { - removeListener(CLICK_EVENT, LayoutClickEvent.class, listener); + removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener); } /** diff --git a/src/com/vaadin/ui/CustomComponent.java b/src/com/vaadin/ui/CustomComponent.java index 21eda08909..98d650f6db 100644 --- a/src/com/vaadin/ui/CustomComponent.java +++ b/src/com/vaadin/ui/CustomComponent.java @@ -7,11 +7,6 @@ package com.vaadin.ui; import java.io.Serializable; import java.util.Iterator; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VCustomComponent; -import com.vaadin.ui.ClientWidget.LoadStyle; - /** * Custom component provides simple implementation of Component interface for * creation of new UI components by composition of existing components. @@ -27,7 +22,6 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VCustomComponent.class, loadStyle = LoadStyle.EAGER) public class CustomComponent extends AbstractComponentContainer { /** @@ -36,11 +30,6 @@ public class CustomComponent extends AbstractComponentContainer { private Component root = null; /** - * Type of the component. - */ - private String componentType = null; - - /** * Constructs a new custom component. * * <p> @@ -107,51 +96,6 @@ public class CustomComponent extends AbstractComponentContainer { /* Basic component features ------------------------------------------ */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - if (root == null) { - throw new IllegalStateException("Composition root must be set to" - + " non-null value before the " + getClass().getName() - + " can be painted"); - } - - if (getComponentType() != null) { - target.addAttribute("type", getComponentType()); - } - root.paint(target); - } - - /** - * Gets the component type. - * - * The component type is textual type of the component. This is included in - * the UIDL as component tag attribute. - * - * @deprecated not more useful as the whole tag system has been removed - * - * @return the component type. - */ - @Deprecated - public String getComponentType() { - return componentType; - } - - /** - * Sets the component type. - * - * The component type is textual type of the component. This is included in - * the UIDL as component tag attribute. - * - * @deprecated not more useful as the whole tag system has been removed - * - * @param componentType - * the componentType to set. - */ - @Deprecated - public void setComponentType(String componentType) { - this.componentType = componentType; - } - private class ComponentIterator implements Iterator<Component>, Serializable { boolean first = getCompositionRoot() != null; diff --git a/src/com/vaadin/ui/CustomField.java b/src/com/vaadin/ui/CustomField.java new file mode 100644 index 0000000000..806ee91335 --- /dev/null +++ b/src/com/vaadin/ui/CustomField.java @@ -0,0 +1,230 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Iterator; + +import com.vaadin.data.Property; + +/** + * A {@link Field} whose UI content can be constructed by the user, enabling the + * creation of e.g. form fields by composing Vaadin components. Customization of + * both the visual presentation and the logic of the field is possible. + * + * Subclasses must implement {@link #getType()} and {@link #initContent()}. + * + * Most custom fields can simply compose a user interface that calls the methods + * {@link #setInternalValue(Object)} and {@link #getInternalValue()} when + * necessary. + * + * It is also possible to override {@link #validate()}, + * {@link #setInternalValue(Object)}, {@link #commit()}, + * {@link #setPropertyDataSource(Property)}, {@link #isEmpty()} and other logic + * of the field. Methods overriding {@link #setInternalValue(Object)} should + * also call the corresponding superclass method. + * + * @param <T> + * field value type + * + * @since 7.0 + */ +public abstract class CustomField<T> extends AbstractField<T> implements + ComponentContainer { + + /** + * The root component implementing the custom component. + */ + private Component root = null; + + /** + * Constructs a new custom field. + * + * <p> + * The component is implemented by wrapping the methods of the composition + * root component given as parameter. The composition root must be set + * before the component can be used. + * </p> + */ + public CustomField() { + // expand horizontally by default + setWidth(100, Unit.PERCENTAGE); + } + + /** + * Constructs the content and notifies it that the {@link CustomField} is + * attached to a window. + * + * @see com.vaadin.ui.Component#attach() + */ + @Override + public void attach() { + root = getContent(); + super.attach(); + getContent().setParent(this); + getContent().attach(); + + fireComponentAttachEvent(getContent()); + } + + /** + * Notifies the content that the {@link CustomField} is detached from a + * window. + * + * @see com.vaadin.ui.Component#detach() + */ + @Override + public void detach() { + super.detach(); + getContent().detach(); + } + + /** + * Returns the content (UI) of the custom component. + * + * @return Component + */ + protected Component getContent() { + if (null == root) { + root = initContent(); + } + return root; + } + + /** + * Create the content component or layout for the field. Subclasses of + * {@link CustomField} should implement this method. + * + * Note that this method is called when the CustomField is attached to a + * layout or when {@link #getContent()} is called explicitly for the first + * time. It is only called once for a {@link CustomField}. + * + * @return {@link Component} representing the UI of the CustomField + */ + protected abstract Component initContent(); + + // Size related methods + // TODO might not be necessary to override but following the pattern from + // AbstractComponentContainer + + @Override + public void setHeight(float height, Unit unit) { + super.setHeight(height, unit); + requestRepaintAll(); + } + + @Override + public void setWidth(float height, Unit unit) { + super.setWidth(height, unit); + requestRepaintAll(); + } + + // ComponentContainer methods + + private class ComponentIterator implements Iterator<Component>, + Serializable { + boolean first = (root != null); + + public boolean hasNext() { + return first; + } + + public Component next() { + first = false; + return getContent(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public Iterator<Component> getComponentIterator() { + return new ComponentIterator(); + } + + public Iterator<Component> iterator() { + return getComponentIterator(); + } + + public int getComponentCount() { + return (null != getContent()) ? 1 : 0; + } + + public void requestRepaintAll() { + AbstractComponentContainer.requestRepaintAll(this); + } + + /** + * Fires the component attached event. This should be called by the + * addComponent methods after the component have been added to this + * container. + * + * @param component + * the component that has been added to this container. + */ + protected void fireComponentAttachEvent(Component component) { + fireEvent(new ComponentAttachEvent(this, component)); + } + + // TODO remove these methods when ComponentContainer interface is cleaned up + + public void addComponent(Component c) { + throw new UnsupportedOperationException(); + } + + public void removeComponent(Component c) { + throw new UnsupportedOperationException(); + } + + public void removeAllComponents() { + throw new UnsupportedOperationException(); + } + + public void replaceComponent(Component oldComponent, Component newComponent) { + throw new UnsupportedOperationException(); + } + + public void moveComponentsFrom(ComponentContainer source) { + throw new UnsupportedOperationException(); + } + + private static final Method COMPONENT_ATTACHED_METHOD; + + static { + try { + COMPONENT_ATTACHED_METHOD = ComponentAttachListener.class + .getDeclaredMethod("componentAttachedToContainer", + new Class[] { ComponentAttachEvent.class }); + } catch (final java.lang.NoSuchMethodException e) { + // This should never happen + throw new java.lang.RuntimeException( + "Internal error finding methods in CustomField"); + } + } + + public void addListener(ComponentAttachListener listener) { + addListener(ComponentContainer.ComponentAttachEvent.class, listener, + COMPONENT_ATTACHED_METHOD); + } + + public void removeListener(ComponentAttachListener listener) { + removeListener(ComponentContainer.ComponentAttachEvent.class, listener, + COMPONENT_ATTACHED_METHOD); + } + + public void addListener(ComponentDetachListener listener) { + // content never detached + } + + public void removeListener(ComponentDetachListener listener) { + // content never detached + } + + public boolean isComponentVisible(Component childComponent) { + return true; + } +} diff --git a/src/com/vaadin/ui/CustomLayout.java b/src/com/vaadin/ui/CustomLayout.java index dc473fb549..97cea1c49d 100644 --- a/src/com/vaadin/ui/CustomLayout.java +++ b/src/com/vaadin/ui/CustomLayout.java @@ -9,10 +9,14 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; +import java.util.Set; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VCustomLayout; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.ui.customlayout.CustomLayoutState; +import com.vaadin.terminal.gwt.server.JsonPaintTarget; /** * <p> @@ -44,8 +48,7 @@ import com.vaadin.terminal.gwt.client.ui.VCustomLayout; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VCustomLayout.class) -public class CustomLayout extends AbstractLayout { +public class CustomLayout extends AbstractLayout implements Vaadin6Component { private static final int BUFFER_SIZE = 10000; @@ -54,10 +57,6 @@ public class CustomLayout extends AbstractLayout { */ private final HashMap<String, Component> slots = new HashMap<String, Component>(); - private String templateContents = null; - - private String templateName = null; - /** * Default constructor only used by subclasses. Subclasses are responsible * for setting the appropriate fields. Either @@ -90,7 +89,7 @@ public class CustomLayout extends AbstractLayout { */ public CustomLayout(String template) { this(); - templateName = template; + setTemplateName(template); } protected void initTemplateContentsFromInputStream( @@ -110,7 +109,12 @@ public class CustomLayout extends AbstractLayout { } } - templateContents = b.toString(); + setTemplateContents(b.toString()); + } + + @Override + public CustomLayoutState getState() { + return (CustomLayoutState) super.getState(); } /** @@ -128,6 +132,7 @@ public class CustomLayout extends AbstractLayout { removeComponent(old); } slots.put(location, c); + getState().getChildLocations().put(c, location); c.setParent(this); fireComponentAttachEvent(c); requestRepaint(); @@ -159,6 +164,7 @@ public class CustomLayout extends AbstractLayout { return; } slots.values().remove(c); + getState().getChildLocations().remove(c); super.removeComponent(c); requestRepaint(); } @@ -205,37 +211,6 @@ public class CustomLayout extends AbstractLayout { return slots.get(location); } - /** - * Paints the content of this component. - * - * @param target - * @throws PaintException - * if the paint operation failed. - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - - if (templateName != null) { - target.addAttribute("template", templateName); - } else { - target.addAttribute("templateContents", templateContents); - } - // Adds all items in all the locations - for (final Iterator<String> i = slots.keySet().iterator(); i.hasNext();) { - // Gets the (location,component) - final String location = i.next(); - final Component c = slots.get(location); - if (c != null) { - // Writes the item - target.startTag("location"); - target.addAttribute("name", location); - c.paint(target); - target.endTag("location"); - } - } - } - /* Documented in superclass */ public void replaceComponent(Component oldComponent, Component newComponent) { @@ -261,32 +236,20 @@ public class CustomLayout extends AbstractLayout { } else { slots.put(newLocation, oldComponent); slots.put(oldLocation, newComponent); + getState().getChildLocations().put(newComponent, oldLocation); + getState().getChildLocations().put(oldComponent, newLocation); requestRepaint(); } } - /** - * CustomLayout's template selecting was previously implemented with - * setStyle. Overriding to improve backwards compatibility. - * - * @param name - * template name - * @deprecated Use {@link #setTemplateName(String)} instead - */ - @Deprecated - @Override - public void setStyle(String name) { - setTemplateName(name); - } - /** Get the name of the template */ public String getTemplateName() { - return templateName; + return getState().getTemplateName(); } /** Get the contents of the template */ public String getTemplateContents() { - return templateContents; + return getState().getTemplateContents(); } /** @@ -299,8 +262,8 @@ public class CustomLayout extends AbstractLayout { * @param templateName */ public void setTemplateName(String templateName) { - this.templateName = templateName; - templateContents = null; + getState().setTemplateName(templateName); + getState().setTemplateContents(null); requestRepaint(); } @@ -310,8 +273,8 @@ public class CustomLayout extends AbstractLayout { * @param templateContents */ public void setTemplateContents(String templateContents) { - this.templateContents = templateContents; - templateName = null; + getState().setTemplateContents(templateContents); + getState().setTemplateName(null); requestRepaint(); } @@ -342,4 +305,20 @@ public class CustomLayout extends AbstractLayout { "CustomLayout does not support margins."); } + public void changeVariables(Object source, Map<String, Object> variables) { + // Nothing to see here + } + + public void paintContent(PaintTarget target) throws PaintException { + // Workaround to make the CommunicationManager read the template file + // and send it to the client + String templateName = getState().getTemplateName(); + if (templateName != null && templateName.length() != 0) { + Set<Object> usedResources = ((JsonPaintTarget) target) + .getUsedResources(); + String resourceName = "layouts/" + templateName + ".html"; + usedResources.add(resourceName); + } + } + } diff --git a/src/com/vaadin/ui/DateField.java b/src/com/vaadin/ui/DateField.java index ef67345aab..55ff67229c 100644 --- a/src/com/vaadin/ui/DateField.java +++ b/src/com/vaadin/ui/DateField.java @@ -4,11 +4,13 @@ package com.vaadin.ui; -import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; @@ -16,6 +18,7 @@ import java.util.TimeZone; import com.vaadin.data.Property; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.converter.Converter; import com.vaadin.event.FieldEvents; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; @@ -23,8 +26,8 @@ import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VDateField; -import com.vaadin.terminal.gwt.client.ui.VPopupCalendar; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.ui.datefield.VDateField; /** * <p> @@ -47,56 +50,128 @@ import com.vaadin.terminal.gwt.client.ui.VPopupCalendar; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VPopupCalendar.class) -public class DateField extends AbstractField implements - FieldEvents.BlurNotifier, FieldEvents.FocusNotifier { - - /* Private members */ +public class DateField extends AbstractField<Date> implements + FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Vaadin6Component { /** - * Resolution identifier: milliseconds. + * Resolutions for DateFields + * + * @author Vaadin Ltd. + * @version + * @VERSION@ + * @since 7.0 */ - public static final int RESOLUTION_MSEC = 0; + public enum Resolution { + SECOND(Calendar.SECOND), MINUTE(Calendar.MINUTE), HOUR( + Calendar.HOUR_OF_DAY), DAY(Calendar.DAY_OF_MONTH), MONTH( + Calendar.MONTH), YEAR(Calendar.YEAR); + + private int calendarField; + + private Resolution(int calendarField) { + this.calendarField = calendarField; + } + + /** + * Returns the field in {@link Calendar} that corresponds to this + * resolution. + * + * @return one of the field numbers used by Calendar + */ + public int getCalendarField() { + return calendarField; + } + + /** + * Returns the resolutions that are higher or equal to the given + * resolution, starting from the given resolution. In other words + * passing DAY to this methods returns DAY,MONTH,YEAR + * + * @param r + * The resolution to start from + * @return An iterable for the resolutions higher or equal to r + */ + public static Iterable<Resolution> getResolutionsHigherOrEqualTo( + Resolution r) { + List<Resolution> resolutions = new ArrayList<DateField.Resolution>(); + Resolution[] values = Resolution.values(); + for (int i = r.ordinal(); i < values.length; i++) { + resolutions.add(values[i]); + } + return resolutions; + } + + /** + * Returns the resolutions that are lower than the given resolution, + * starting from the given resolution. In other words passing DAY to + * this methods returns HOUR,MINUTE,SECOND. + * + * @param r + * The resolution to start from + * @return An iterable for the resolutions lower than r + */ + public static List<Resolution> getResolutionsLowerThan(Resolution r) { + List<Resolution> resolutions = new ArrayList<DateField.Resolution>(); + Resolution[] values = Resolution.values(); + for (int i = r.ordinal() - 1; i >= 0; i--) { + resolutions.add(values[i]); + } + return resolutions; + } + }; /** * Resolution identifier: seconds. + * + * @deprecated Use {@link Resolution#SECOND} */ - public static final int RESOLUTION_SEC = 1; + @Deprecated + public static final Resolution RESOLUTION_SEC = Resolution.SECOND; /** * Resolution identifier: minutes. + * + * @deprecated Use {@link Resolution#MINUTE} */ - public static final int RESOLUTION_MIN = 2; + @Deprecated + public static final Resolution RESOLUTION_MIN = Resolution.MINUTE; /** * Resolution identifier: hours. + * + * @deprecated Use {@link Resolution#HOUR} */ - public static final int RESOLUTION_HOUR = 3; + @Deprecated + public static final Resolution RESOLUTION_HOUR = Resolution.HOUR; /** * Resolution identifier: days. + * + * @deprecated Use {@link Resolution#DAY} */ - public static final int RESOLUTION_DAY = 4; + @Deprecated + public static final Resolution RESOLUTION_DAY = Resolution.DAY; /** * Resolution identifier: months. + * + * @deprecated Use {@link Resolution#MONTH} */ - public static final int RESOLUTION_MONTH = 5; + @Deprecated + public static final Resolution RESOLUTION_MONTH = Resolution.MONTH; /** * Resolution identifier: years. + * + * @deprecated Use {@link Resolution#YEAR} */ - public static final int RESOLUTION_YEAR = 6; - - /** - * Specified smallest modifiable unit. - */ - private int resolution = RESOLUTION_MSEC; + @Deprecated + public static final Resolution RESOLUTION_YEAR = Resolution.YEAR; /** - * Specified largest modifiable unit. + * Specified smallest modifiable unit for the date field. */ - private static final int largestModifiable = RESOLUTION_YEAR; + private Resolution resolution = Resolution.DAY; /** * The internal calendar to be used in java.utl.Date conversions. @@ -129,6 +204,16 @@ public class DateField extends AbstractField implements private TimeZone timeZone = null; + private static Map<Resolution, String> variableNameForResolution = new HashMap<DateField.Resolution, String>(); + { + variableNameForResolution.put(Resolution.SECOND, "sec"); + variableNameForResolution.put(Resolution.MINUTE, "min"); + variableNameForResolution.put(Resolution.HOUR, "hour"); + variableNameForResolution.put(Resolution.DAY, "day"); + variableNameForResolution.put(Resolution.MONTH, "month"); + variableNameForResolution.put(Resolution.YEAR, "year"); + } + /* Constructors */ /** @@ -201,9 +286,7 @@ public class DateField extends AbstractField implements * Paints this component. Don't add a JavaDoc comment here, we use the * default documentation from implemented interface. */ - @Override public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); // Adds the locale as attribute final Locale l = getLocale(); @@ -228,51 +311,21 @@ public class DateField extends AbstractField implements // Gets the calendar final Calendar calendar = getCalendar(); - final Date currentDate = (Date) getValue(); - - for (int r = resolution; r <= largestModifiable; r++) { - switch (r) { - case RESOLUTION_MSEC: - target.addVariable( - this, - "msec", - currentDate != null ? calendar - .get(Calendar.MILLISECOND) : -1); - break; - case RESOLUTION_SEC: - target.addVariable(this, "sec", - currentDate != null ? calendar.get(Calendar.SECOND) - : -1); - break; - case RESOLUTION_MIN: - target.addVariable(this, "min", - currentDate != null ? calendar.get(Calendar.MINUTE) - : -1); - break; - case RESOLUTION_HOUR: - target.addVariable( - this, - "hour", - currentDate != null ? calendar - .get(Calendar.HOUR_OF_DAY) : -1); - break; - case RESOLUTION_DAY: - target.addVariable( - this, - "day", - currentDate != null ? calendar - .get(Calendar.DAY_OF_MONTH) : -1); - break; - case RESOLUTION_MONTH: - target.addVariable(this, "month", - currentDate != null ? calendar.get(Calendar.MONTH) + 1 - : -1); - break; - case RESOLUTION_YEAR: - target.addVariable(this, "year", - currentDate != null ? calendar.get(Calendar.YEAR) : -1); - break; + final Date currentDate = getValue(); + + // Only paint variables for the resolution and up, e.g. Resolution DAY + // paints DAY,MONTH,YEAR + for (Resolution res : Resolution + .getResolutionsHigherOrEqualTo(resolution)) { + int value = -1; + if (currentDate != null) { + value = calendar.get(res.getCalendarField()); + if (res == Resolution.MONTH) { + // Calendar month is zero based + value++; + } } + target.addVariable(this, variableNameForResolution.get(res), value); } } @@ -286,9 +339,7 @@ public class DateField extends AbstractField implements * comment here, we use the default documentation from implemented * interface. */ - @Override public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); if (!isReadOnly() && (variables.containsKey("year") @@ -298,10 +349,10 @@ public class DateField extends AbstractField implements || variables.containsKey("min") || variables.containsKey("sec") || variables.containsKey("msec") || variables - .containsKey("dateString"))) { + .containsKey("dateString"))) { // Old and new dates - final Date oldDate = (Date) getValue(); + final Date oldDate = getValue(); Date newDate = null; // this enables analyzing invalid input on the server @@ -309,59 +360,50 @@ public class DateField extends AbstractField implements dateString = newDateString; // Gets the new date in parts - // Null values are converted to negative values. - int year = variables.containsKey("year") ? (variables.get("year") == null ? -1 - : ((Integer) variables.get("year")).intValue()) - : -1; - int month = variables.containsKey("month") ? (variables - .get("month") == null ? -1 : ((Integer) variables - .get("month")).intValue() - 1) : -1; - int day = variables.containsKey("day") ? (variables.get("day") == null ? -1 - : ((Integer) variables.get("day")).intValue()) - : -1; - int hour = variables.containsKey("hour") ? (variables.get("hour") == null ? -1 - : ((Integer) variables.get("hour")).intValue()) - : -1; - int min = variables.containsKey("min") ? (variables.get("min") == null ? -1 - : ((Integer) variables.get("min")).intValue()) - : -1; - int sec = variables.containsKey("sec") ? (variables.get("sec") == null ? -1 - : ((Integer) variables.get("sec")).intValue()) - : -1; - int msec = variables.containsKey("msec") ? (variables.get("msec") == null ? -1 - : ((Integer) variables.get("msec")).intValue()) - : -1; - - // If all of the components is < 0 use the previous value - if (year < 0 && month < 0 && day < 0 && hour < 0 && min < 0 - && sec < 0 && msec < 0) { + boolean hasChanges = false; + Map<Resolution, Integer> calendarFieldChanges = new HashMap<DateField.Resolution, Integer>(); + + for (Resolution r : Resolution + .getResolutionsHigherOrEqualTo(resolution)) { + // Only handle what the client is allowed to send. The same + // resolutions that are painted + String variableName = variableNameForResolution.get(r); + + if (variables.containsKey(variableName)) { + Integer value = (Integer) variables.get(variableName); + if (r == Resolution.MONTH) { + // Calendar MONTH is zero based + value--; + } + if (value >= 0) { + hasChanges = true; + calendarFieldChanges.put(r, value); + } + } + } + + // If no new variable values were received, use the previous value + if (!hasChanges) { newDate = null; } else { - // Clone the calendar for date operation final Calendar cal = getCalendar(); - // Make sure that meaningful values exists - // Use the previous value if some of the variables - // have not been changed. - year = year < 0 ? cal.get(Calendar.YEAR) : year; - month = month < 0 ? cal.get(Calendar.MONTH) : month; - day = day < 0 ? cal.get(Calendar.DAY_OF_MONTH) : day; - hour = hour < 0 ? cal.get(Calendar.HOUR_OF_DAY) : hour; - min = min < 0 ? cal.get(Calendar.MINUTE) : min; - sec = sec < 0 ? cal.get(Calendar.SECOND) : sec; - msec = msec < 0 ? cal.get(Calendar.MILLISECOND) : msec; - - // Sets the calendar fields - cal.set(Calendar.YEAR, year); - cal.set(Calendar.MONTH, month); - cal.set(Calendar.DAY_OF_MONTH, day); - cal.set(Calendar.HOUR_OF_DAY, hour); - cal.set(Calendar.MINUTE, min); - cal.set(Calendar.SECOND, sec); - cal.set(Calendar.MILLISECOND, msec); - - // Assigns the date + // Update the value based on the received info + // Must set in this order to avoid invalid dates (or wrong + // dates if lenient is true) in calendar + for (int r = Resolution.YEAR.ordinal(); r >= 0; r--) { + Resolution res = Resolution.values()[r]; + if (calendarFieldChanges.containsKey(res)) { + + // Field resolution should be included. Others are + // skipped so that client can not make unexpected + // changes (e.g. day change even though resolution is + // year). + Integer newValue = calendarFieldChanges.get(res); + cal.set(res.getCalendarField(), newValue); + } + } newDate = cal.getTime(); } @@ -377,7 +419,7 @@ public class DateField extends AbstractField implements * this case the invalid text remains in the DateField. */ requestRepaint(); - } catch (ConversionException e) { + } catch (Converter.ConversionException e) { /* * Datefield now contains some text that could't be parsed @@ -445,7 +487,7 @@ public class DateField extends AbstractField implements * This method is called to handle a non-empty date string from the client * if the client could not parse it as a Date. * - * By default, a Property.ConversionException is thrown, and the current + * By default, a Converter.ConversionException is thrown, and the current * value is not modified. * * This can be overridden to handle conversions, to return null (equivalent @@ -453,13 +495,13 @@ public class DateField extends AbstractField implements * * @param dateString * @return parsed Date - * @throws Property.ConversionException + * @throws Converter.ConversionException * to keep the old value and indicate an error */ protected Date handleUnparsableDateString(String dateString) - throws Property.ConversionException { + throws Converter.ConversionException { currentParseErrorMessage = null; - throw new Property.ConversionException(getParseErrorMessage()); + throw new Converter.ConversionException(getParseErrorMessage()); } /* Property features */ @@ -469,7 +511,7 @@ public class DateField extends AbstractField implements * the default documentation from implemented interface. */ @Override - public Class<?> getType() { + public Class<Date> getType() { return Date.class; } @@ -479,8 +521,8 @@ public class DateField extends AbstractField implements * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object, boolean) */ @Override - protected void setValue(Object newValue, boolean repaintIsNotNeeded) - throws Property.ReadOnlyException, Property.ConversionException { + protected void setValue(Date newValue, boolean repaintIsNotNeeded) + throws Property.ReadOnlyException { /* * First handle special case when the client side component have a date @@ -513,23 +555,7 @@ public class DateField extends AbstractField implements return; } - if (newValue == null || newValue instanceof Date) { - super.setValue(newValue, repaintIsNotNeeded); - } else { - // Try to parse the given string value to Date - try { - final SimpleDateFormat parser = new SimpleDateFormat(); - final TimeZone currentTimeZone = getTimeZone(); - if (currentTimeZone != null) { - parser.setTimeZone(currentTimeZone); - } - final Date val = parser.parse(newValue.toString()); - super.setValue(val, repaintIsNotNeeded); - } catch (final ParseException e) { - uiHasValidDateString = false; - throw new Property.ConversionException(getParseErrorMessage()); - } - } + super.setValue(newValue, repaintIsNotNeeded); } /** @@ -544,7 +570,7 @@ public class DateField extends AbstractField implements Form f = (Form) parenOfDateField; Collection<?> visibleItemProperties = f.getItemPropertyIds(); for (Object fieldId : visibleItemProperties) { - Field field = f.getField(fieldId); + Field<?> field = f.getField(fieldId); if (field == this) { /* * this datefield is logically in a form. Do the same @@ -564,24 +590,8 @@ public class DateField extends AbstractField implements } } - /** - * Sets the DateField datasource. Datasource type must assignable to Date. - * - * @see com.vaadin.data.Property.Viewer#setPropertyDataSource(Property) - */ @Override - public void setPropertyDataSource(Property newDataSource) { - if (newDataSource == null - || Date.class.isAssignableFrom(newDataSource.getType())) { - super.setPropertyDataSource(newDataSource); - } else { - throw new IllegalArgumentException( - "DateField only supports Date properties"); - } - } - - @Override - protected void setInternalValue(Object newValue) { + protected void setInternalValue(Date newValue) { // Also set the internal dateString if (newValue != null) { dateString = newValue.toString(); @@ -604,17 +614,19 @@ public class DateField extends AbstractField implements * * @return int */ - public int getResolution() { + public Resolution getResolution() { return resolution; } /** * Sets the resolution of the DateField. * + * The default resolution is {@link Resolution#DAY} since Vaadin 7.0. + * * @param resolution * the resolution to set. */ - public void setResolution(int resolution) { + public void setResolution(Resolution resolution) { this.resolution = resolution; requestRepaint(); } @@ -636,13 +648,19 @@ public class DateField extends AbstractField implements // Makes sure we have an calendar instance if (calendar == null) { calendar = Calendar.getInstance(); + // Start by a zeroed calendar to avoid having values for lower + // resolution variables e.g. time when resolution is day + for (Resolution r : Resolution.getResolutionsLowerThan(resolution)) { + calendar.set(r.getCalendarField(), 0); + } + calendar.set(Calendar.MILLISECOND, 0); } // Clone the instance final Calendar newCal = (Calendar) calendar.clone(); // Assigns the current time tom calendar. - final Date currentDate = (Date) getValue(); + final Date currentDate = getValue(); if (currentDate != null) { newCal.setTime(currentDate); } @@ -752,19 +770,14 @@ public class DateField extends AbstractField implements } /** - * Tests the current value against registered validators if the field is not - * empty. Note that DateField is considered empty (value == null) and + * Validates the current value against registered validators if the field is + * not empty. Note that DateField is considered empty (value == null) and * invalid if it contains text typed in by the user that couldn't be parsed * into a Date value. * - * @see com.vaadin.ui.AbstractField#isValid() + * @see com.vaadin.ui.AbstractField#validate() */ @Override - public boolean isValid() { - return uiHasValidDateString && super.isValid(); - } - - @Override public void validate() throws InvalidValueException { /* * To work properly in form we must throw exception if there is diff --git a/src/com/vaadin/ui/DefaultFieldFactory.java b/src/com/vaadin/ui/DefaultFieldFactory.java index 1e55d2795f..9d096094e3 100644 --- a/src/com/vaadin/ui/DefaultFieldFactory.java +++ b/src/com/vaadin/ui/DefaultFieldFactory.java @@ -35,19 +35,20 @@ public class DefaultFieldFactory implements FormFieldFactory, TableFieldFactory protected DefaultFieldFactory() { } - public Field createField(Item item, Object propertyId, Component uiContext) { + public Field<?> createField(Item item, Object propertyId, + Component uiContext) { Class<?> type = item.getItemProperty(propertyId).getType(); - Field field = createFieldByPropertyType(type); + Field<?> field = createFieldByPropertyType(type); field.setCaption(createCaptionByPropertyId(propertyId)); return field; } - public Field createField(Container container, Object itemId, + public Field<?> createField(Container container, Object itemId, Object propertyId, Component uiContext) { - Property containerProperty = container.getContainerProperty(itemId, + Property<?> containerProperty = container.getContainerProperty(itemId, propertyId); Class<?> type = containerProperty.getType(); - Field field = createFieldByPropertyType(type); + Field<?> field = createFieldByPropertyType(type); field.setCaption(createCaptionByPropertyId(propertyId)); return field; } @@ -63,6 +64,10 @@ public class DefaultFieldFactory implements FormFieldFactory, TableFieldFactory String name = propertyId.toString(); if (name.length() > 0) { + int dotLocation = name.lastIndexOf('.'); + if (dotLocation > 0 && dotLocation < name.length() - 1) { + name = name.substring(dotLocation + 1); + } if (name.indexOf(' ') < 0 && name.charAt(0) == Character.toLowerCase(name.charAt(0)) && name.charAt(0) != Character.toUpperCase(name.charAt(0))) { @@ -110,7 +115,7 @@ public class DefaultFieldFactory implements FormFieldFactory, TableFieldFactory * the type of the property * @return the most suitable generic {@link Field} for given type */ - public static Field createFieldByPropertyType(Class<?> type) { + public static Field<?> createFieldByPropertyType(Class<?> type) { // Null typed properties can not be edited if (type == null) { return null; diff --git a/src/com/vaadin/ui/DirtyConnectorTracker.java b/src/com/vaadin/ui/DirtyConnectorTracker.java new file mode 100644 index 0000000000..84df7e7c7c --- /dev/null +++ b/src/com/vaadin/ui/DirtyConnectorTracker.java @@ -0,0 +1,132 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.vaadin.terminal.gwt.server.AbstractCommunicationManager; +import com.vaadin.terminal.gwt.server.ClientConnector; +import com.vaadin.ui.Component.RepaintRequestEvent; +import com.vaadin.ui.Component.RepaintRequestListener; + +/** + * A class that tracks dirty {@link ClientConnector}s. A {@link ClientConnector} + * is dirty when an operation has been performed on it on the server and as a + * result of this operation new information needs to be sent to its client side + * counterpart. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class DirtyConnectorTracker implements RepaintRequestListener { + private Set<Component> dirtyComponents = new HashSet<Component>(); + private Root root; + + /** + * Gets a logger for this class + * + * @return A logger instance for logging within this class + * + */ + public static Logger getLogger() { + return Logger.getLogger(DirtyConnectorTracker.class.getName()); + } + + public DirtyConnectorTracker(Root root) { + this.root = root; + } + + public void repaintRequested(RepaintRequestEvent event) { + markDirty((Component) event.getConnector()); + } + + public void componentAttached(Component component) { + component.addListener(this); + markDirty(component); + } + + private void markDirty(Component component) { + if (getLogger().isLoggable(Level.FINE)) { + if (!dirtyComponents.contains(component)) { + getLogger() + .fine(getDebugInfo(component) + " " + "is now dirty"); + } + } + + dirtyComponents.add(component); + } + + private void markClean(Component component) { + if (getLogger().isLoggable(Level.FINE)) { + if (dirtyComponents.contains(component)) { + getLogger().fine( + getDebugInfo(component) + " " + "is no longer dirty"); + } + } + + dirtyComponents.remove(component); + } + + private String getDebugInfo(Component component) { + String message = getObjectString(component); + if (component.getParent() != null) { + message += " (parent: " + getObjectString(component.getParent()) + + ")"; + } + return message; + } + + private String getObjectString(Object component) { + return component.getClass().getName() + "@" + + Integer.toHexString(component.hashCode()); + } + + public void componentDetached(Component component) { + component.removeListener(this); + markClean(component); + } + + public void markAllComponentsDirty() { + markComponentsDirtyRecursively(root); + getLogger().fine("All components are now dirty"); + } + + public void markAllComponentsClean() { + dirtyComponents.clear(); + getLogger().fine("All components are now clean"); + } + + /** + * Marks all visible components dirty, starting from the given component and + * going downwards in the hierarchy. + * + * @param c + * The component to start iterating downwards from + */ + private void markComponentsDirtyRecursively(Component c) { + if (!c.isVisible()) { + return; + } + markDirty(c); + if (c instanceof HasComponents) { + HasComponents container = (HasComponents) c; + for (Component child : AbstractCommunicationManager + .getChildComponents(container)) { + markComponentsDirtyRecursively(child); + } + } + + } + + public Collection<Component> getDirtyComponents() { + return dirtyComponents; + } + +} diff --git a/src/com/vaadin/ui/DragAndDropWrapper.java b/src/com/vaadin/ui/DragAndDropWrapper.java index c6522f15c7..b623197a4c 100644 --- a/src/com/vaadin/ui/DragAndDropWrapper.java +++ b/src/com/vaadin/ui/DragAndDropWrapper.java @@ -20,15 +20,15 @@ import com.vaadin.event.dd.TargetDetailsImpl; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.StreamVariable; +import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper; import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; +import com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper; @SuppressWarnings("serial") -@ClientWidget(VDragAndDropWrapper.class) public class DragAndDropWrapper extends CustomComponent implements DropTarget, - DragSource { + DragSource, Vaadin6Component { public class WrapperTransferable extends TransferableImpl { @@ -214,9 +214,11 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, requestRepaint(); } - @Override + public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Remove once Vaadin6Component is no longer implemented + } + public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); target.addAttribute(VDragAndDropWrapper.DRAG_START_MODE, dragStartMode.ordinal()); if (getDropHandler() != null) { diff --git a/src/com/vaadin/ui/Embedded.java b/src/com/vaadin/ui/Embedded.java index 181cbbfb96..1bcd984666 100644 --- a/src/com/vaadin/ui/Embedded.java +++ b/src/com/vaadin/ui/Embedded.java @@ -13,8 +13,11 @@ 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.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VEmbedded; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.embedded.EmbeddedConnector; +import com.vaadin.terminal.gwt.client.ui.embedded.EmbeddedServerRpc; /** * Component for embedding external objects. @@ -25,10 +28,7 @@ import com.vaadin.terminal.gwt.client.ui.VEmbedded; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VEmbedded.class) -public class Embedded extends AbstractComponent { - - private static final String CLICK_EVENT = VEmbedded.CLICK_EVENT_IDENTIFIER; +public class Embedded extends AbstractComponent implements Vaadin6Component { /** * General object type. @@ -80,10 +80,17 @@ public class Embedded extends AbstractComponent { private String altText; + private EmbeddedServerRpc rpc = new EmbeddedServerRpc() { + public void click(MouseEventDetails mouseDetails) { + fireEvent(new ClickEvent(Embedded.this, mouseDetails)); + } + }; + /** * Creates a new empty Embedded object. */ public Embedded() { + registerRpc(rpc); } /** @@ -92,6 +99,7 @@ public class Embedded extends AbstractComponent { * @param caption */ public Embedded(String caption) { + this(); setCaption(caption); } @@ -105,14 +113,13 @@ public class Embedded extends AbstractComponent { * the Source of the embedded object. */ public Embedded(String caption, Resource source) { - setCaption(caption); + this(caption); setSource(source); } /** * Invoked when the component state should be painted. */ - @Override public void paintContent(PaintTarget target) throws PaintException { switch (type) { @@ -149,7 +156,7 @@ public class Embedded extends AbstractComponent { target.addAttribute("archive", archive); } if (altText != null && !"".equals(altText)) { - target.addAttribute(VEmbedded.ALTERNATE_TEXT, altText); + target.addAttribute(EmbeddedConnector.ALTERNATE_TEXT, altText); } // Params @@ -498,8 +505,8 @@ public class Embedded extends AbstractComponent { * The listener to add */ public void addListener(ClickListener listener) { - addListener(CLICK_EVENT, ClickEvent.class, listener, - ClickListener.clickMethod); + addListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, ClickEvent.class, + listener, ClickListener.clickMethod); } /** @@ -510,35 +517,12 @@ public class Embedded extends AbstractComponent { * The listener to remove */ public void removeListener(ClickListener listener) { - removeListener(CLICK_EVENT, ClickEvent.class, listener); + removeListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, + ClickEvent.class, listener); } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, - * java.util.Map) - */ - @SuppressWarnings("unchecked") - @Override public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); - if (variables.containsKey(CLICK_EVENT)) { - fireClick((Map<String, Object>) variables.get(CLICK_EVENT)); - } - - } - - /** - * Notifies click-listeners that a mouse click event has occurred. - * - * @param parameters - */ - private void fireClick(Map<String, Object> parameters) { - MouseEventDetails mouseDetails = MouseEventDetails - .deSerialize((String) parameters.get("mouseDetails")); - - fireEvent(new ClickEvent(this, mouseDetails)); + // TODO Remove once Vaadin6Component is no longer implemented } } diff --git a/src/com/vaadin/ui/ExpandLayout.java b/src/com/vaadin/ui/ExpandLayout.java deleted file mode 100644 index 55ee2ffdcf..0000000000 --- a/src/com/vaadin/ui/ExpandLayout.java +++ /dev/null @@ -1,101 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.ui; - -/** - * A layout that will give one of it's components as much space as possible, - * while still showing the other components in the layout. The other components - * will in effect be given a fixed sized space, while the space given to the - * expanded component will grow/shrink to fill the rest of the space available - - * for instance when re-sizing the window. - * - * Note that this layout is 100% in both directions by default ({link - * {@link #setSizeFull()}). Remember to set the units if you want to specify a - * fixed size. If the layout fails to show up, check that the parent layout is - * actually giving some space. - * - * @deprecated Deprecated in favor of the new OrderedLayout - */ -@SuppressWarnings("serial") -@Deprecated -public class ExpandLayout extends OrderedLayout { - - private Component expanded = null; - - public ExpandLayout() { - this(ORIENTATION_VERTICAL); - } - - public ExpandLayout(int orientation) { - super(orientation); - - setSizeFull(); - } - - /** - * @param c - * Component which container will be maximized - */ - public void expand(Component c) { - if (expanded != null) { - try { - setExpandRatio(expanded, 0.0f); - } catch (IllegalArgumentException e) { - // Ignore error if component has been removed - } - } - - expanded = c; - if (expanded != null) { - setExpandRatio(expanded, 1.0f); - } - - requestRepaint(); - } - - @Override - public void addComponent(Component c, int index) { - super.addComponent(c, index); - if (expanded == null) { - expand(c); - } - } - - @Override - public void addComponent(Component c) { - super.addComponent(c); - if (expanded == null) { - expand(c); - } - } - - @Override - public void addComponentAsFirst(Component c) { - super.addComponentAsFirst(c); - if (expanded == null) { - expand(c); - } - } - - @Override - public void removeComponent(Component c) { - super.removeComponent(c); - if (c == expanded) { - if (getComponentIterator().hasNext()) { - expand(getComponentIterator().next()); - } else { - expand(null); - } - } - } - - @Override - public void replaceComponent(Component oldComponent, Component newComponent) { - super.replaceComponent(oldComponent, newComponent); - if (oldComponent == expanded) { - expand(newComponent); - } - } -} diff --git a/src/com/vaadin/ui/Field.java b/src/com/vaadin/ui/Field.java index 0cd6cd2d87..3a66db47b0 100644 --- a/src/com/vaadin/ui/Field.java +++ b/src/com/vaadin/ui/Field.java @@ -9,30 +9,21 @@ import com.vaadin.data.Property; import com.vaadin.ui.Component.Focusable; /** + * TODO document + * * @author Vaadin Ltd. * + * @param T + * the type of values in the field, which might not be the same type + * as that of the data source if converters are used + * + * @author IT Mill Ltd. */ -public interface Field extends Component, BufferedValidatable, Property, +public interface Field<T> extends Component, BufferedValidatable, Property<T>, Property.ValueChangeNotifier, Property.ValueChangeListener, Property.Editor, Focusable { /** - * Sets the Caption. - * - * @param caption - */ - void setCaption(String caption); - - String getDescription(); - - /** - * Sets the Description. - * - * @param caption - */ - void setDescription(String caption); - - /** * Is this field required. * * Required fields must filled by the user. @@ -80,7 +71,7 @@ public interface Field extends Component, BufferedValidatable, Property, * @since 3.0 */ @SuppressWarnings("serial") - public class ValueChangeEvent extends Component.Event implements + public static class ValueChangeEvent extends Component.Event implements Property.ValueChangeEvent { /** diff --git a/src/com/vaadin/ui/FieldFactory.java b/src/com/vaadin/ui/FieldFactory.java deleted file mode 100644 index d18918640e..0000000000 --- a/src/com/vaadin/ui/FieldFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.ui; - -import com.vaadin.data.Property; - -/** - * Factory for creating new Field-instances based on type, datasource and/or - * context. - * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 3.1 - * @deprecated FieldFactory was split into two lighter interfaces in 6.0 Use - * FormFieldFactory or TableFieldFactory or both instead. - */ -@Deprecated -public interface FieldFactory extends FormFieldFactory, TableFieldFactory { - - /** - * Creates a field based on type of data. - * - * @param type - * the type of data presented in field. - * @param uiContext - * the component where the field is presented. - * @return Field the field suitable for editing the specified data. - * - */ - Field createField(Class<?> type, Component uiContext); - - /** - * Creates a field based on the property datasource. - * - * @param property - * the property datasource. - * @param uiContext - * the component where the field is presented. - * @return Field the field suitable for editing the specified data. - */ - Field createField(Property property, Component uiContext); - -}
\ No newline at end of file diff --git a/src/com/vaadin/ui/Form.java b/src/com/vaadin/ui/Form.java index c3bb725edf..5f5516b21f 100644 --- a/src/com/vaadin/ui/Form.java +++ b/src/com/vaadin/ui/Form.java @@ -4,6 +4,7 @@ package com.vaadin.ui; +import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -17,16 +18,20 @@ import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.fieldgroup.FieldGroup; import com.vaadin.data.util.BeanItem; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.Action.ShortcutNotifier; import com.vaadin.event.ActionManager; +import com.vaadin.terminal.AbstractErrorMessage; import com.vaadin.terminal.CompositeErrorMessage; import com.vaadin.terminal.ErrorMessage; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VForm; +import com.vaadin.terminal.UserError; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.ui.form.FormState; /** * Form component provides easy way of creating and managing sets fields. @@ -58,20 +63,17 @@ import com.vaadin.terminal.gwt.client.ui.VForm; * @version * @VERSION@ * @since 3.0 + * @deprecated Use {@link FieldGroup} instead of {@link Form} for more + * flexibility. */ -@SuppressWarnings("serial") -@ClientWidget(VForm.class) -public class Form extends AbstractField implements Item.Editor, Buffered, Item, - Validatable, Action.Notifier { +@Deprecated +public class Form extends AbstractField<Object> implements Item.Editor, + Buffered, Item, Validatable, Action.Notifier, HasComponents, + Vaadin6Component { private Object propertyValue; /** - * Layout of the form. - */ - private Layout layout; - - /** * Item connected to this form as datasource. */ private Item itemDatasource; @@ -99,12 +101,12 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, /** * Mapping from propertyName to corresponding field. */ - private final HashMap<Object, Field> fields = new HashMap<Object, Field>(); + private final HashMap<Object, Field<?>> fields = new HashMap<Object, Field<?>>(); /** * Form may act as an Item, its own properties are stored here. */ - private final HashMap<Object, Property> ownProperties = new HashMap<Object, Property>(); + private final HashMap<Object, Property<?>> ownProperties = new HashMap<Object, Property<?>>(); /** * Field factory for this form. @@ -129,8 +131,6 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } }; - private Layout formFooter; - /** * If this is true, commit implicitly calls setValidationVisible(true). */ @@ -182,30 +182,25 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, public Form(Layout formLayout, FormFieldFactory fieldFactory) { super(); setLayout(formLayout); + setFooter(null); setFormFieldFactory(fieldFactory); setValidationVisible(false); setWidth(100, UNITS_PERCENTAGE); } - /* Documented in interface */ @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - - layout.paint(target); - if (formFooter != null) { - formFooter.paint(target); - } + public FormState getState() { + return (FormState) super.getState(); + } + /* Documented in interface */ + public void paintContent(PaintTarget target) throws PaintException { if (ownActionManager != null) { ownActionManager.paintActions(null, target); } } - @Override public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); - // Actions if (ownActionManager != null) { ownActionManager.handleActions(variables, this); @@ -238,15 +233,13 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, if (validationError != null) { // Show caption as error for fields with empty errors if ("".equals(validationError.toString())) { - validationError = new Validator.InvalidValueException( - field.getCaption()); + validationError = new UserError(field.getCaption()); } break; - } else if (f instanceof Field && !((Field) f).isValid()) { + } else if (f instanceof Field && !((Field<?>) f).isValid()) { // Something is wrong with the field, but no proper // error is given. Generate one. - validationError = new Validator.InvalidValueException( - field.getCaption()); + validationError = new UserError(field.getCaption()); break; } } @@ -260,9 +253,12 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } // Throw combination of the error types - return new CompositeErrorMessage(new ErrorMessage[] { - getComponentError(), validationError, - currentBufferedSourceException }); + return new CompositeErrorMessage( + new ErrorMessage[] { + getComponentError(), + validationError, + AbstractErrorMessage + .getErrorMessageForException(currentBufferedSourceException) }); } /** @@ -321,7 +317,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, // Try to commit all for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) { try { - final Field f = (fields.get(i.next())); + final Field<?> f = (fields.get(i.next())); // Commit only non-readonly fields. if (!f.isReadOnly()) { f.commit(); @@ -408,7 +404,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, @Override public boolean isModified() { for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) { - final Field f = fields.get(i.next()); + final Field<?> f = fields.get(i.next()); if (f != null && f.isModified()) { return true; } @@ -422,6 +418,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * we use the default one from the interface. */ @Override + @Deprecated public boolean isReadThrough() { return readThrough; } @@ -431,6 +428,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * we use the default one from the interface. */ @Override + @Deprecated public boolean isWriteThrough() { return writeThrough; } @@ -485,7 +483,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, ownProperties.put(id, property); // Gets suitable field - final Field field = fieldFactory.createField(this, id, this); + final Field<?> field = fieldFactory.createField(this, id, this); if (field == null) { return false; } @@ -516,7 +514,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * @param field * the field which should be added to the form. */ - public void addField(Object propertyId, Field field) { + public void addField(Object propertyId, Field<?> field) { registerField(propertyId, field); attachField(propertyId, field); requestRepaint(); @@ -536,7 +534,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * @param field * the Field that should be registered */ - private void registerField(Object propertyId, Field field) { + private void registerField(Object propertyId, Field<?> field) { if (propertyId == null || field == null) { return; } @@ -580,6 +578,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, return; } + Layout layout = getLayout(); if (layout instanceof CustomLayout) { ((CustomLayout) layout).addComponent(field, propertyId.toString()); } else { @@ -599,13 +598,13 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * * @see com.vaadin.data.Item#getItemProperty(Object) */ - public Property getItemProperty(Object id) { - final Field field = fields.get(id); + public Property<?> getItemProperty(Object id) { + final Field<?> field = fields.get(id); if (field == null) { // field does not exist or it is not (yet) created for this property return ownProperties.get(id); } - final Property property = field.getPropertyDataSource(); + final Property<?> property = field.getPropertyDataSource(); if (property != null) { return property; @@ -620,7 +619,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * @param propertyId * the id of the property. */ - public Field getField(Object propertyId) { + public Field<?> getField(Object propertyId) { return fields.get(propertyId); } @@ -637,7 +636,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, public boolean removeItemProperty(Object id) { ownProperties.remove(id); - final Field field = fields.get(id); + final Field<?> field = fields.get(id); if (field != null) { propertyIds.remove(id); @@ -722,8 +721,8 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, */ public void setItemDataSource(Item newDataSource, Collection<?> propertyIds) { - if (layout instanceof GridLayout) { - GridLayout gl = (GridLayout) layout; + if (getLayout() instanceof GridLayout) { + GridLayout gl = (GridLayout) getLayout(); if (gridlayoutCursorX == -1) { // first setItemDataSource, remember initial cursor gridlayoutCursorX = gl.getCursorX(); @@ -750,9 +749,9 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, // Adds all the properties to this form for (final Iterator<?> i = propertyIds.iterator(); i.hasNext();) { final Object id = i.next(); - final Property property = itemDatasource.getItemProperty(id); + final Property<?> property = itemDatasource.getItemProperty(id); if (id != null && property != null) { - final Field f = fieldFactory.createField(itemDatasource, id, + final Field<?> f = fieldFactory.createField(itemDatasource, id, this); if (f != null) { bindPropertyToField(id, property, f); @@ -799,25 +798,24 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * @return the Layout of the form. */ public Layout getLayout() { - return layout; + return (Layout) getState().getLayout(); } /** * Sets the layout of the form. * * <p> - * By default form uses <code>OrderedLayout</code> with <code>form</code> - * -style. + * If set to null then Form uses a FormLayout by default. * </p> * - * @param newLayout - * the Layout of the form. + * @param layout + * the layout of the form. */ - public void setLayout(Layout newLayout) { + public void setLayout(Layout layout) { // Use orderedlayout by default - if (newLayout == null) { - newLayout = new FormLayout(); + if (layout == null) { + layout = new FormLayout(); } // reset cursor memory @@ -825,26 +823,29 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, gridlayoutCursorY = -1; // Move fields from previous layout - if (layout != null) { + if (getLayout() != null) { final Object[] properties = propertyIds.toArray(); for (int i = 0; i < properties.length; i++) { - Field f = getField(properties[i]); + Field<?> f = getField(properties[i]); detachField(f); - if (newLayout instanceof CustomLayout) { - ((CustomLayout) newLayout).addComponent(f, + if (layout instanceof CustomLayout) { + ((CustomLayout) layout).addComponent(f, properties[i].toString()); } else { - newLayout.addComponent(f); + layout.addComponent(f); } } - layout.setParent(null); + getLayout().setParent(null); } // Replace the previous layout - newLayout.setParent(this); - layout = newLayout; + layout.setParent(this); + getState().setLayout(layout); + // Hierarchy has changed so we need to repaint (this could be a + // hierarchy repaint only) + requestRepaint(); } /** @@ -856,13 +857,16 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * match. Null values are not supported. * </p> * + * Note: since Vaadin 7.0, returns an {@link AbstractSelect} instead of a + * {@link Select}. + * * @param propertyId * the id of the property. * @param values * @param descriptions * @return the select property generated */ - public Select replaceWithSelect(Object propertyId, Object[] values, + public AbstractSelect replaceWithSelect(Object propertyId, Object[] values, Object[] descriptions) { // Checks the parameters @@ -875,7 +879,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } // Gets the old field - final Field oldField = fields.get(propertyId); + final Field<?> oldField = fields.get(propertyId); if (oldField == null) { throw new IllegalArgumentException("Field with given propertyid '" + propertyId.toString() + "' can not be found."); @@ -922,10 +926,8 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } // Creates the new field matching to old field parameters - final Select newField = new Select(); - if (isMultiselect) { - newField.setMultiSelect(true); - } + final AbstractSelect newField = isMultiselect ? new ListSelect() + : new Select(); newField.setCaption(oldField.getCaption()); newField.setReadOnly(oldField.isReadOnly()); newField.setReadThrough(oldField.isReadThrough()); @@ -952,12 +954,12 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } // Sets the property data source - final Property property = oldField.getPropertyDataSource(); + final Property<?> property = oldField.getPropertyDataSource(); oldField.setPropertyDataSource(null); newField.setPropertyDataSource(property); // Replaces the old field with new one - layout.replaceComponent(oldField, newField); + getLayout().replaceComponent(oldField, newField); fields.put(propertyId, newField); newField.addListener(fieldValueChangeListener); oldField.removeListener(fieldValueChangeListener); @@ -973,9 +975,9 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, @Override public void attach() { super.attach(); - layout.attach(); - if (formFooter != null) { - formFooter.attach(); + getLayout().attach(); + if (getFooter() != null) { + getFooter().attach(); } } @@ -987,28 +989,14 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, @Override public void detach() { super.detach(); - layout.detach(); - if (formFooter != null) { - formFooter.detach(); + getLayout().detach(); + if (getFooter() != null) { + getFooter().detach(); } } /** - * Tests the current value of the object against all registered validators - * - * @see com.vaadin.data.Validatable#isValid() - */ - @Override - public boolean isValid() { - boolean valid = true; - for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) { - valid &= (fields.get(i.next())).isValid(); - } - return valid && super.isValid(); - } - - /** - * Checks the validity of the validatable. + * Checks the validity of the Form and all of its fields. * * @see com.vaadin.data.Validatable#validate() */ @@ -1055,23 +1043,6 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } /** - * Sets the field factory of Form. - * - * <code>FieldFactory</code> is used to create fields for form properties. - * By default the form uses BaseFieldFactory to create Field instances. - * - * @param fieldFactory - * the New factory used to create the fields. - * @see Field - * @see FormFieldFactory - * @deprecated use {@link #setFormFieldFactory(FormFieldFactory)} instead - */ - @Deprecated - public void setFieldFactory(FieldFactory fieldFactory) { - this.fieldFactory = fieldFactory; - } - - /** * Sets the field factory used by this Form to genarate Fields for * properties. * @@ -1097,23 +1068,6 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } /** - * Get the field factory of the form. - * - * @return the FieldFactory Factory used to create the fields. - * @deprecated Use {@link #getFormFieldFactory()} instead. Set the - * FormFieldFactory using - * {@link #setFormFieldFactory(FormFieldFactory)}. - */ - @Deprecated - public FieldFactory getFieldFactory() { - if (fieldFactory instanceof FieldFactory) { - return (FieldFactory) fieldFactory; - - } - return null; - } - - /** * Gets the field type. * * @see com.vaadin.ui.AbstractField#getType() @@ -1155,11 +1109,11 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * * @return the Field. */ - private Field getFirstFocusableField() { + private Field<?> getFirstFocusableField() { if (getItemPropertyIds() != null) { for (Object id : getItemPropertyIds()) { if (id != null) { - Field field = getField(id); + Field<?> field = getField(id); if (field.isEnabled() && !field.isReadOnly()) { return field; } @@ -1248,7 +1202,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, */ @Override public void focus() { - final Field f = getFirstFocusableField(); + final Field<?> f = getFirstFocusableField(); if (f != null) { f.focus(); } @@ -1274,8 +1228,8 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, @Override public void setImmediate(boolean immediate) { super.setImmediate(immediate); - for (Iterator<Field> i = fields.values().iterator(); i.hasNext();) { - Field f = i.next(); + for (Iterator<Field<?>> i = fields.values().iterator(); i.hasNext();) { + Field<?> f = i.next(); if (f instanceof AbstractComponent) { ((AbstractComponent) f).setImmediate(immediate); } @@ -1286,10 +1240,10 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, @Override protected boolean isEmpty() { - for (Iterator<Field> i = fields.values().iterator(); i.hasNext();) { - Field f = i.next(); + for (Iterator<Field<?>> i = fields.values().iterator(); i.hasNext();) { + Field<?> f = i.next(); if (f instanceof AbstractField) { - if (!((AbstractField) f).isEmpty()) { + if (!((AbstractField<?>) f).isEmpty()) { return false; } } @@ -1315,25 +1269,32 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, * @return layout rendered below normal form contents. */ public Layout getFooter() { - if (formFooter == null) { - formFooter = new HorizontalLayout(); - formFooter.setParent(this); - } - return formFooter; + return (Layout) getState().getFooter(); } /** - * Sets the layout that is rendered below normal form contens. + * Sets the layout that is rendered below normal form contents. Setting this + * to null will cause an empty HorizontalLayout to be rendered in the + * footer. * - * @param newFormFooter - * the new Layout + * @param footer + * the new footer layout */ - public void setFooter(Layout newFormFooter) { - if (formFooter != null) { - formFooter.setParent(null); + public void setFooter(Layout footer) { + if (getFooter() != null) { + getFooter().setParent(null); } - formFooter = newFormFooter; - formFooter.setParent(this); + if (footer == null) { + footer = new HorizontalLayout(); + } + + getState().setFooter(footer); + footer.setParent(this); + + // Hierarchy has changed so we need to repaint (this could be a + // hierarchy repaint only) + requestRepaint(); + } @Override @@ -1399,4 +1360,74 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } } + public Iterator<Component> iterator() { + return getComponentIterator(); + } + + /** + * Modifiable and Serializable Iterator for the components, used by + * {@link Form#getComponentIterator()}. + */ + private class ComponentIterator implements Iterator<Component>, + Serializable { + + int i = 0; + + public boolean hasNext() { + if (i < getComponentCount()) { + return true; + } + return false; + } + + public Component next() { + if (!hasNext()) { + return null; + } + i++; + if (i == 1) { + return getLayout() != null ? getLayout() : getFooter(); + } else if (i == 2) { + return getFooter(); + } + return null; + } + + public void remove() { + if (i == 1) { + if (getLayout() != null) { + setLayout(null); + i = 0; + } else { + setFooter(null); + } + } else if (i == 2) { + setFooter(null); + } + } + } + + public Iterator<Component> getComponentIterator() { + return new ComponentIterator(); + } + + public void requestRepaintAll() { + AbstractComponentContainer.requestRepaintAll(this); + } + + public int getComponentCount() { + int count = 0; + if (getLayout() != null) { + count++; + } + if (getFooter() != null) { + count++; + } + + return count; + } + + public boolean isComponentVisible(Component childComponent) { + return true; + }; } diff --git a/src/com/vaadin/ui/FormFieldFactory.java b/src/com/vaadin/ui/FormFieldFactory.java index 52ecfcd8c2..1efa05c5f5 100644 --- a/src/com/vaadin/ui/FormFieldFactory.java +++ b/src/com/vaadin/ui/FormFieldFactory.java @@ -37,5 +37,5 @@ public interface FormFieldFactory extends Serializable { * creating it. * @return Field the field suitable for editing the specified data. */ - Field createField(Item item, Object propertyId, Component uiContext); + Field<?> createField(Item item, Object propertyId, Component uiContext); } diff --git a/src/com/vaadin/ui/FormLayout.java b/src/com/vaadin/ui/FormLayout.java index f61f5d544e..c0be784a7b 100644 --- a/src/com/vaadin/ui/FormLayout.java +++ b/src/com/vaadin/ui/FormLayout.java @@ -4,8 +4,6 @@ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VFormLayout; - /** * FormLayout is used by {@link Form} to layout fields. It may also be used * separately without {@link Form}. @@ -21,14 +19,13 @@ import com.vaadin.terminal.gwt.client.ui.VFormLayout; * bottom are by default on. * */ -@SuppressWarnings({ "deprecation", "serial" }) -@ClientWidget(VFormLayout.class) -public class FormLayout extends OrderedLayout { +public class FormLayout extends AbstractOrderedLayout { public FormLayout() { super(); setSpacing(true); setMargin(true, false, true, false); + setWidth(100, UNITS_PERCENTAGE); } } diff --git a/src/com/vaadin/ui/GridLayout.java b/src/com/vaadin/ui/GridLayout.java index 90122cddf9..0ab729ce5c 100644 --- a/src/com/vaadin/ui/GridLayout.java +++ b/src/com/vaadin/ui/GridLayout.java @@ -15,10 +15,15 @@ import java.util.Map.Entry; import com.vaadin.event.LayoutEvents.LayoutClickEvent; import com.vaadin.event.LayoutEvents.LayoutClickListener; import com.vaadin.event.LayoutEvents.LayoutClickNotifier; +import com.vaadin.terminal.LegacyPaint; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.ui.VGridLayout; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.gridlayout.GridLayoutServerRpc; +import com.vaadin.terminal.gwt.client.ui.gridlayout.GridLayoutState; /** * A layout where the components are laid out on a grid using cell coordinates. @@ -47,22 +52,19 @@ import com.vaadin.terminal.gwt.client.ui.VGridLayout; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VGridLayout.class) public class GridLayout extends AbstractLayout implements - Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier { + Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier, + Vaadin6Component { - private static final String CLICK_EVENT = EventId.LAYOUT_CLICK; + private GridLayoutServerRpc rpc = new GridLayoutServerRpc() { - /** - * Initial grid columns. - */ - private int cols = 0; - - /** - * Initial grid rows. - */ - private int rows = 0; + public void layoutClick(MouseEventDetails mouseDetails, + Connector clickedConnector) { + fireEvent(LayoutClickEvent.createEvent(GridLayout.this, + mouseDetails, clickedConnector)); + } + }; /** * Cursor X position: this is where the next component with unspecified x,y * is inserted @@ -91,11 +93,6 @@ public class GridLayout extends AbstractLayout implements */ private Map<Component, Alignment> componentToAlignment = new HashMap<Component, Alignment>(); - /** - * Is spacing between contained components enabled. Defaults to false. - */ - private boolean spacing = false; - private static final Alignment ALIGNMENT_DEFAULT = Alignment.TOP_LEFT; /** @@ -121,6 +118,7 @@ public class GridLayout extends AbstractLayout implements public GridLayout(int columns, int rows) { setColumns(columns); setRows(rows); + registerRpc(rpc); } /** @@ -130,6 +128,11 @@ public class GridLayout extends AbstractLayout implements this(1, 1); } + @Override + public GridLayoutState getState() { + return (GridLayoutState) super.getState(); + } + /** * <p> * Adds a component to the grid in the specified area. The area is defined @@ -185,7 +188,8 @@ public class GridLayout extends AbstractLayout implements throw new IllegalArgumentException( "Illegal coordinates for the component"); } - if (column1 < 0 || row1 < 0 || column2 >= cols || row2 >= rows) { + if (column1 < 0 || row1 < 0 || column2 >= getColumns() + || row2 >= getRows()) { throw new OutOfBoundsException(area); } @@ -228,7 +232,7 @@ public class GridLayout extends AbstractLayout implements && cursorY <= row2) { // cursor within area cursorX = column2 + 1; // one right of area - if (cursorX >= cols) { + if (cursorX >= getColumns()) { // overflowed columns cursorX = 0; // first col // move one row down, or one row under the area @@ -310,7 +314,7 @@ public class GridLayout extends AbstractLayout implements */ public void space() { cursorX++; - if (cursorX >= cols) { + if (cursorX >= getColumns()) { cursorX = 0; cursorY++; } @@ -342,8 +346,12 @@ public class GridLayout extends AbstractLayout implements } // Extends the grid if needed - cols = cursorX >= cols ? cursorX + 1 : cols; - rows = cursorY >= rows ? cursorY + 1 : rows; + if (cursorX >= getColumns()) { + setColumns(cursorX + 1); + } + if (cursorY >= getRows()) { + setRows(cursorY + 1); + } addComponent(component, cursorX, cursorY); } @@ -423,6 +431,10 @@ public class GridLayout extends AbstractLayout implements return components.size(); } + public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Remove once Vaadin6Component is no longer implemented + } + /** * Paints the contents of this component. * @@ -431,22 +443,11 @@ public class GridLayout extends AbstractLayout implements * @throws PaintException * if the paint operation failed. */ - @Override public void paintContent(PaintTarget target) throws PaintException { - - super.paintContent(target); - // TODO refactor attribute names in future release. - target.addAttribute("h", rows); - target.addAttribute("w", cols); - target.addAttribute("structuralChange", structuralChange); structuralChange = false; - if (spacing) { - target.addAttribute("spacing", spacing); - } - // Area iterator final Iterator<Area> areaiterator = areas.iterator(); @@ -460,22 +461,22 @@ public class GridLayout extends AbstractLayout implements int emptyCells = 0; final String[] alignmentsArray = new String[components.size()]; - final Integer[] columnExpandRatioArray = new Integer[cols]; - final Integer[] rowExpandRatioArray = new Integer[rows]; + final Integer[] columnExpandRatioArray = new Integer[getColumns()]; + final Integer[] rowExpandRatioArray = new Integer[getRows()]; int realColExpandRatioSum = 0; float colSum = getExpandRatioSum(columnExpandRatio); if (colSum == 0) { // no columns has been expanded, all cols have same expand // rate - float equalSize = 1 / (float) cols; + float equalSize = 1 / (float) getColumns(); int myRatio = Math.round(equalSize * 1000); - for (int i = 0; i < cols; i++) { + for (int i = 0; i < getColumns(); i++) { columnExpandRatioArray[i] = myRatio; } - realColExpandRatioSum = myRatio * cols; + realColExpandRatioSum = myRatio * getColumns(); } else { - for (int i = 0; i < cols; i++) { + for (int i = 0; i < getColumns(); i++) { int myRatio = Math .round((getColumnExpandRatio(i) / colSum) * 1000); columnExpandRatioArray[i] = myRatio; @@ -489,18 +490,18 @@ public class GridLayout extends AbstractLayout implements if (rowSum == 0) { // no rows have been expanded equallyDividedRows = true; - float equalSize = 1 / (float) rows; + float equalSize = 1 / (float) getRows(); int myRatio = Math.round(equalSize * 1000); - for (int i = 0; i < rows; i++) { + for (int i = 0; i < getRows(); i++) { rowExpandRatioArray[i] = myRatio; } - realRowExpandRatioSum = myRatio * rows; + realRowExpandRatioSum = myRatio * getRows(); } int index = 0; // Iterates every applicable row - for (int cury = 0; cury < rows; cury++) { + for (int cury = 0; cury < getRows(); cury++) { target.startTag("gr"); if (!equallyDividedRows) { @@ -511,7 +512,7 @@ public class GridLayout extends AbstractLayout implements } // Iterates every applicable column - for (int curx = 0; curx < cols; curx++) { + for (int curx = 0; curx < getColumns(); curx++) { // Checks if current item is located at curx,cury if (area != null && (area.row1 == cury) @@ -543,7 +544,7 @@ public class GridLayout extends AbstractLayout implements if (rows > 1) { target.addAttribute("h", rows); } - area.getComponent().paint(target); + LegacyPaint.paint(area.getComponent(), target); alignmentsArray[index++] = String .valueOf(getComponentAlignment(area.getComponent()) @@ -621,7 +622,7 @@ public class GridLayout extends AbstractLayout implements // Checks if empty cell needs to be rendered if (emptyCells > 0) { target.startTag("gc"); - target.addAttribute("x", cols - emptyCells); + target.addAttribute("x", getColumns() - emptyCells); target.addAttribute("y", cury); if (emptyCells > 1) { target.addAttribute("w", emptyCells); @@ -967,12 +968,12 @@ public class GridLayout extends AbstractLayout implements } // In case of no change - if (cols == columns) { + if (getColumns() == columns) { return; } // Checks for overlaps - if (cols > columns) { + if (getColumns() > columns) { for (final Iterator<Area> i = areas.iterator(); i.hasNext();) { final Area area = i.next(); if (area.column2 >= columns) { @@ -981,7 +982,7 @@ public class GridLayout extends AbstractLayout implements } } - cols = columns; + getState().setColumns(columns); requestRepaint(); } @@ -992,7 +993,7 @@ public class GridLayout extends AbstractLayout implements * @return the number of columns in the grid. */ public int getColumns() { - return cols; + return getState().getColumns(); } /** @@ -1011,12 +1012,12 @@ public class GridLayout extends AbstractLayout implements } // In case of no change - if (this.rows == rows) { + if (getRows() == rows) { return; } // Checks for overlaps - if (this.rows > rows) { + if (getRows() > rows) { for (final Iterator<Area> i = areas.iterator(); i.hasNext();) { final Area area = i.next(); if (area.row2 >= rows) { @@ -1025,7 +1026,7 @@ public class GridLayout extends AbstractLayout implements } } - this.rows = rows; + getState().setRows(rows); requestRepaint(); } @@ -1036,7 +1037,7 @@ public class GridLayout extends AbstractLayout implements * @return the number of rows in the grid. */ public int getRows() { - return rows; + return getState().getRows(); } /** @@ -1160,8 +1161,8 @@ public class GridLayout extends AbstractLayout implements * * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean) */ - public void setSpacing(boolean enabled) { - spacing = enabled; + public void setSpacing(boolean spacing) { + getState().setSpacing(spacing); requestRepaint(); } @@ -1170,18 +1171,8 @@ public class GridLayout extends AbstractLayout implements * * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing() */ - @Deprecated - public boolean isSpacingEnabled() { - return spacing; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing() - */ public boolean isSpacing() { - return spacing; + return getState().isSpacing(); } /** @@ -1192,9 +1183,9 @@ public class GridLayout extends AbstractLayout implements * The leftmost row has index 0. */ public void insertRow(int row) { - if (row > rows) { + if (row > getRows()) { throw new IllegalArgumentException("Cannot insert row at " + row - + " in a gridlayout with height " + rows); + + " in a gridlayout with height " + getRows()); } for (Iterator<Area> i = areas.iterator(); i.hasNext();) { @@ -1215,7 +1206,7 @@ public class GridLayout extends AbstractLayout implements cursorY++; } - setRows(rows + 1); + setRows(getRows() + 1); structuralChange = true; requestRepaint(); } @@ -1238,9 +1229,9 @@ public class GridLayout extends AbstractLayout implements * Index of the row to remove. The leftmost row has index 0. */ public void removeRow(int row) { - if (row >= rows) { + if (row >= getRows()) { throw new IllegalArgumentException("Cannot delete row " + row - + " from a gridlayout with height " + rows); + + " from a gridlayout with height " + getRows()); } // Remove all components in row @@ -1260,7 +1251,7 @@ public class GridLayout extends AbstractLayout implements } } - if (rows == 1) { + if (getRows() == 1) { /* * Removing the last row means that the dimensions of the Grid * layout will be truncated to 1 empty row and the cursor is moved @@ -1269,7 +1260,7 @@ public class GridLayout extends AbstractLayout implements cursorX = 0; cursorY = 0; } else { - setRows(rows - 1); + setRows(getRows() - 1); if (cursorY > row) { cursorY--; } @@ -1397,29 +1388,15 @@ public class GridLayout extends AbstractLayout implements return null; } - /** - * Sets the component alignment using a shorthand string notation. - * - * @deprecated Replaced by - * {@link #setComponentAlignment(Component, Alignment)} - * - * @param component - * A child component in this layout - * @param alignment - * A short hand notation described in {@link AlignmentUtils} - */ - @Deprecated - public void setComponentAlignment(Component component, String alignment) { - AlignmentUtils.setComponentAlignment(this, component, alignment); - } - public void addListener(LayoutClickListener listener) { - addListener(CLICK_EVENT, LayoutClickEvent.class, listener, + addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener, LayoutClickListener.clickMethod); } public void removeListener(LayoutClickListener listener) { - removeListener(CLICK_EVENT, LayoutClickEvent.class, listener); + removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER, + LayoutClickEvent.class, listener); } } diff --git a/src/com/vaadin/ui/HasComponents.java b/src/com/vaadin/ui/HasComponents.java new file mode 100644 index 0000000000..eca89ddcd2 --- /dev/null +++ b/src/com/vaadin/ui/HasComponents.java @@ -0,0 +1,54 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import java.util.Iterator; + +/** + * Interface that must be implemented by all {@link Component}s that contain + * other {@link Component}s. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public interface HasComponents extends Component, Iterable<Component> { + /** + * Gets an iterator to the collection of contained components. Using this + * iterator it is possible to step through all components contained in this + * container. + * + * @return the component iterator. + */ + public Iterator<Component> getComponentIterator(); + + /** + * Checks if the child component is visible. This method allows hiding a + * child component from updates and communication to and from the client. + * This is useful for components that show only a limited number of its + * children at any given time and want to allow updates only for the + * children that are visible (e.g. TabSheet has one tab open at a time). + * <p> + * Note that this will prevent updates from reaching the child even though + * the child itself is set to visible. Also if a child is set to invisible + * this will not force it to be visible. + * </p> + * + * @param childComponent + * The child component to check + * @return true if the child component is visible to the user, false + * otherwise + */ + public boolean isComponentVisible(Component childComponent); + + /** + * Causes a repaint of this component, and all components below it. + * + * This should only be used in special cases, e.g when the state of a + * descendant depends on the state of a ancestor. + */ + public void requestRepaintAll(); + +} diff --git a/src/com/vaadin/ui/HorizontalLayout.java b/src/com/vaadin/ui/HorizontalLayout.java index ed1cad8184..b9dc1c13ca 100644 --- a/src/com/vaadin/ui/HorizontalLayout.java +++ b/src/com/vaadin/ui/HorizontalLayout.java @@ -3,9 +3,6 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VHorizontalLayout; -import com.vaadin.ui.ClientWidget.LoadStyle; - /** * Horizontal layout * @@ -18,7 +15,6 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 5.3 */ @SuppressWarnings("serial") -@ClientWidget(value = VHorizontalLayout.class, loadStyle = LoadStyle.EAGER) public class HorizontalLayout extends AbstractOrderedLayout { public HorizontalLayout() { diff --git a/src/com/vaadin/ui/HorizontalSplitPanel.java b/src/com/vaadin/ui/HorizontalSplitPanel.java index d9368635df..5bd6c8a075 100644 --- a/src/com/vaadin/ui/HorizontalSplitPanel.java +++ b/src/com/vaadin/ui/HorizontalSplitPanel.java @@ -3,9 +3,6 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VSplitPanelHorizontal; -import com.vaadin.ui.ClientWidget.LoadStyle; - /** * A horizontal split panel contains two components and lays them horizontally. * The first component is on the left side. @@ -29,7 +26,6 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @VERSION@ * @since 6.5 */ -@ClientWidget(value = VSplitPanelHorizontal.class, loadStyle = LoadStyle.EAGER) public class HorizontalSplitPanel extends AbstractSplitPanel { public HorizontalSplitPanel() { super(); diff --git a/src/com/vaadin/ui/InlineDateField.java b/src/com/vaadin/ui/InlineDateField.java index 50e16d803b..cf61703318 100644 --- a/src/com/vaadin/ui/InlineDateField.java +++ b/src/com/vaadin/ui/InlineDateField.java @@ -7,7 +7,6 @@ package com.vaadin.ui; import java.util.Date; import com.vaadin.data.Property; -import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar; /** * <p> @@ -22,7 +21,6 @@ import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar; * @VERSION@ * @since 5.0 */ -@ClientWidget(VDateFieldCalendar.class) public class InlineDateField extends DateField { public InlineDateField() { diff --git a/src/com/vaadin/ui/Label.java b/src/com/vaadin/ui/Label.java index 2dadf4f5c5..e52090aa5f 100644 --- a/src/com/vaadin/ui/Label.java +++ b/src/com/vaadin/ui/Label.java @@ -5,19 +5,18 @@ package com.vaadin.ui; import java.lang.reflect.Method; +import java.util.Map; import com.vaadin.data.Property; import com.vaadin.data.util.ObjectProperty; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VLabel; -import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.terminal.Vaadin6Component; /** * Label component for showing non-editable short texts. * - * The label content can be set to the modes specified by the final members - * CONTENT_* + * The label content can be set to the modes specified by {@link ContentMode} * * <p> * The contents of the label may contain simple formatting: @@ -39,67 +38,165 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VLabel.class, loadStyle = LoadStyle.EAGER) +// TODO generics for interface Property public class Label extends AbstractComponent implements Property, Property.Viewer, Property.ValueChangeListener, - Property.ValueChangeNotifier, Comparable<Object> { + Property.ValueChangeNotifier, Comparable<Object>, Vaadin6Component { /** - * Content mode, where the label contains only plain text. The getValue() - * result is coded to XML when painting. + * Content modes defining how the client should interpret a Label's value. + * + * @sine 7.0 */ - public static final int CONTENT_TEXT = 0; + public enum ContentMode { + /** + * Content mode, where the label contains only plain text. The + * getValue() result is coded to XML when painting. + */ + TEXT(null) { + @Override + public void paintText(String text, PaintTarget target) + throws PaintException { + target.addText(text); + } + }, + + /** + * Content mode, where the label contains preformatted text. + */ + PREFORMATTED("pre") { + @Override + public void paintText(String text, PaintTarget target) + throws PaintException { + target.startTag("pre"); + target.addText(text); + target.endTag("pre"); + } + }, + + /** + * Content mode, where the label contains XHTML. Contents is then + * enclosed in DIV elements having namespace of + * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd". + */ + XHTML("xhtml") { + @Override + public void paintText(String text, PaintTarget target) + throws PaintException { + target.startTag("data"); + target.addXMLSection("div", text, + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); + target.endTag("data"); + } + }, + + /** + * Content mode, where the label contains well-formed or well-balanced + * XML. Each of the root elements must have their default namespace + * specified. + */ + XML("xml") { + @Override + public void paintText(String text, PaintTarget target) + throws PaintException { + target.addXMLSection("data", text, null); + } + }, + + /** + * Content mode, where the label contains RAW output. Output is not + * required to comply to with XML. In Web Adapter output is inserted + * inside the resulting HTML document as-is. This is useful for some + * specific purposes where possibly broken HTML content needs to be + * shown, but in most cases XHTML mode should be preferred. + */ + RAW("raw") { + @Override + public void paintText(String text, PaintTarget target) + throws PaintException { + target.startTag("data"); + target.addAttribute("escape", false); + target.addText(text); + target.endTag("data"); + } + }; + + private final String uidlName; + + /** + * The default content mode is text + */ + public static ContentMode DEFAULT = TEXT; + + private ContentMode(String uidlName) { + this.uidlName = uidlName; + } + + /** + * Gets the name representing this content mode in UIDL messages + * + * @return the UIDL name of this content mode + */ + public String getUidlName() { + return uidlName; + } + + /** + * Adds the text value to a {@link PaintTarget} according to this + * content mode + * + * @param text + * the text to add + * @param target + * the paint target to add the value to + * @throws PaintException + * if the paint operation failed + */ + public abstract void paintText(String text, PaintTarget target) + throws PaintException; + } /** - * Content mode, where the label contains preformatted text. + * @deprecated From 7.0, use {@link ContentMode#TEXT} instead */ - public static final int CONTENT_PREFORMATTED = 1; + @Deprecated + public static final ContentMode CONTENT_TEXT = ContentMode.TEXT; /** - * Formatted content mode, where the contents is XML restricted to the UIDL - * 1.0 formatting markups. - * - * @deprecated Use CONTENT_XML instead. + * @deprecated From 7.0, use {@link ContentMode#PREFORMATTED} instead */ @Deprecated - public static final int CONTENT_UIDL = 2; + public static final ContentMode CONTENT_PREFORMATTED = ContentMode.PREFORMATTED; /** - * Content mode, where the label contains XHTML. Contents is then enclosed - * in DIV elements having namespace of - * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd". + * @deprecated From 7.0, use {@link ContentMode#XHTML} instead */ - public static final int CONTENT_XHTML = 3; + @Deprecated + public static final ContentMode CONTENT_XHTML = ContentMode.XHTML; /** - * Content mode, where the label contains well-formed or well-balanced XML. - * Each of the root elements must have their default namespace specified. + * @deprecated From 7.0, use {@link ContentMode#XML} instead */ - public static final int CONTENT_XML = 4; + @Deprecated + public static final ContentMode CONTENT_XML = ContentMode.XML; /** - * Content mode, where the label contains RAW output. Output is not required - * to comply to with XML. In Web Adapter output is inserted inside the - * resulting HTML document as-is. This is useful for some specific purposes - * where possibly broken HTML content needs to be shown, but in most cases - * XHTML mode should be preferred. + * @deprecated From 7.0, use {@link ContentMode#RAW} instead */ - public static final int CONTENT_RAW = 5; + @Deprecated + public static final ContentMode CONTENT_RAW = ContentMode.RAW; /** - * The default content mode is plain text. + * @deprecated From 7.0, use {@link ContentMode#DEFAULT} instead */ - public static final int CONTENT_DEFAULT = CONTENT_TEXT; - - /** Array of content mode names that are rendered in UIDL as mode attribute. */ - private static final String[] CONTENT_MODE_NAME = { "text", "pre", "uidl", - "xhtml", "xml", "raw" }; + @Deprecated + public static final ContentMode CONTENT_DEFAULT = ContentMode.DEFAULT; private static final String DATASOURCE_MUST_BE_SET = "Datasource must be set"; private Property dataSource; - private int contentMode = CONTENT_DEFAULT; + private ContentMode contentMode = ContentMode.DEFAULT; /** * Creates an empty Label. @@ -114,7 +211,7 @@ public class Label extends AbstractComponent implements Property, * @param content */ public Label(String content) { - this(content, CONTENT_DEFAULT); + this(content, ContentMode.DEFAULT); } /** @@ -124,7 +221,7 @@ public class Label extends AbstractComponent implements Property, * @param contentSource */ public Label(Property contentSource) { - this(contentSource, CONTENT_DEFAULT); + this(contentSource, ContentMode.DEFAULT); } /** @@ -133,7 +230,7 @@ public class Label extends AbstractComponent implements Property, * @param content * @param contentMode */ - public Label(String content, int contentMode) { + public Label(String content, ContentMode contentMode) { this(new ObjectProperty<String>(content, String.class), contentMode); } @@ -144,43 +241,15 @@ public class Label extends AbstractComponent implements Property, * @param contentSource * @param contentMode */ - public Label(Property contentSource, int contentMode) { + public Label(Property contentSource, ContentMode contentMode) { setPropertyDataSource(contentSource); - if (contentMode != CONTENT_DEFAULT) { + if (contentMode != ContentMode.DEFAULT) { setContentMode(contentMode); } setWidth(100, UNITS_PERCENTAGE); } /** - * Set the component to read-only. Readonly is not used in label. - * - * @param readOnly - * True to enable read-only mode, False to disable it. - */ - @Override - public void setReadOnly(boolean readOnly) { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); - } - dataSource.setReadOnly(readOnly); - } - - /** - * Is the component read-only ? Readonly is not used in label - this returns - * allways false. - * - * @return <code>true</code> if the component is in read only mode. - */ - @Override - public boolean isReadOnly() { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); - } - return dataSource.isReadOnly(); - } - - /** * Paints the content of this component. * * @param target @@ -188,32 +257,12 @@ public class Label extends AbstractComponent implements Property, * @throws PaintException * if the Paint Operation fails. */ - @Override public void paintContent(PaintTarget target) throws PaintException { - if (contentMode != CONTENT_TEXT) { - target.addAttribute("mode", CONTENT_MODE_NAME[contentMode]); - } - if (contentMode == CONTENT_TEXT) { - target.addText(toString()); - } else if (contentMode == CONTENT_UIDL) { - target.addUIDL(toString()); - } else if (contentMode == CONTENT_XHTML) { - target.startTag("data"); - target.addXMLSection("div", toString(), - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); - target.endTag("data"); - } else if (contentMode == CONTENT_PREFORMATTED) { - target.startTag("pre"); - target.addText(toString()); - target.endTag("pre"); - } else if (contentMode == CONTENT_XML) { - target.addXMLSection("data", toString(), null); - } else if (contentMode == CONTENT_RAW) { - target.startTag("data"); - target.addAttribute("escape", false); - target.addText(toString()); - target.endTag("data"); + String uidlName = contentMode.getUidlName(); + if (uidlName != null) { + target.addAttribute("mode", uidlName); } + contentMode.paintText(getStringValue(), target); } @@ -246,13 +295,33 @@ public class Label extends AbstractComponent implements Property, /** * @see java.lang.Object#toString() + * @deprecated use the data source value or {@link #getStringValue()} + * instead */ + @Deprecated @Override public String toString() { + throw new UnsupportedOperationException( + "Use Property.getValue() instead of Label.toString()"); + } + + /** + * Returns the value of the <code>Property</code> in human readable textual + * format. + * + * This method exists to help migration from previous Vaadin versions by + * providing a simple replacement for {@link #toString()}. However, it is + * normally better to use the value of the label directly. + * + * @return String representation of the value stored in the Property + * @since 7.0 + */ + public String getStringValue() { if (dataSource == null) { throw new IllegalStateException(DATASOURCE_MUST_BE_SET); } - return dataSource.toString(); + Object value = dataSource.getValue(); + return (null != value) ? value.toString() : null; } /** @@ -307,67 +376,27 @@ public class Label extends AbstractComponent implements Property, /** * Gets the content mode of the Label. * - * <p> - * Possible content modes include: - * <ul> - * <li><b>CONTENT_TEXT</b> Content mode, where the label contains only plain - * text. The getValue() result is coded to XML when painting.</li> - * <li><b>CONTENT_PREFORMATTED</b> Content mode, where the label contains - * preformatted text.</li> - * <li><b>CONTENT_UIDL</b> Formatted content mode, where the contents is XML - * restricted to the UIDL 1.0 formatting markups.</li> - * <li><b>CONTENT_XHTML</b> Content mode, where the label contains XHTML. - * Contents is then enclosed in DIV elements having namespace of - * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".</li> - * <li><b>CONTENT_XML</b> Content mode, where the label contains well-formed - * or well-balanced XML. Each of the root elements must have their default - * namespace specified.</li> - * <li><b>CONTENT_RAW</b> Content mode, where the label contains RAW output. - * Output is not required to comply to with XML. In Web Adapter output is - * inserted inside the resulting HTML document as-is. This is useful for - * some specific purposes where possibly broken HTML content needs to be - * shown, but in most cases XHTML mode should be preferred.</li> - * </ul> - * </p> - * * @return the Content mode of the label. + * + * @see ContentMode */ - public int getContentMode() { + public ContentMode getContentMode() { return contentMode; } /** * Sets the content mode of the Label. * - * <p> - * Possible content modes include: - * <ul> - * <li><b>CONTENT_TEXT</b> Content mode, where the label contains only plain - * text. The getValue() result is coded to XML when painting.</li> - * <li><b>CONTENT_PREFORMATTED</b> Content mode, where the label contains - * preformatted text.</li> - * <li><b>CONTENT_UIDL</b> Formatted content mode, where the contents is XML - * restricted to the UIDL 1.0 formatting markups.</li> - * <li><b>CONTENT_XHTML</b> Content mode, where the label contains XHTML. - * Contents is then enclosed in DIV elements having namespace of - * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".</li> - * <li><b>CONTENT_XML</b> Content mode, where the label contains well-formed - * or well-balanced XML. Each of the root elements must have their default - * namespace specified.</li> - * <li><b>CONTENT_RAW</b> Content mode, where the label contains RAW output. - * Output is not required to comply to with XML. In Web Adapter output is - * inserted inside the resulting HTML document as-is. This is useful for - * some specific purposes where possibly broken HTML content needs to be - * shown, but in most cases XHTML mode should be preferred.</li> - * </ul> - * </p> - * * @param contentMode * the New content mode of the label. + * + * @see ContentMode */ - public void setContentMode(int contentMode) { - if (contentMode != this.contentMode && contentMode >= CONTENT_TEXT - && contentMode <= CONTENT_RAW) { + public void setContentMode(ContentMode contentMode) { + if (contentMode == null) { + throw new IllegalArgumentException("Content mode can not be null"); + } + if (contentMode != this.contentMode) { this.contentMode = contentMode; requestRepaint(); } @@ -397,7 +426,7 @@ public class Label extends AbstractComponent implements Property, * @VERSION@ * @since 3.0 */ - public class ValueChangeEvent extends Component.Event implements + public static class ValueChangeEvent extends Component.Event implements Property.ValueChangeEvent { /** @@ -487,19 +516,19 @@ public class Label extends AbstractComponent implements Property, String thisValue; String otherValue; - if (contentMode == CONTENT_XML || contentMode == CONTENT_UIDL - || contentMode == CONTENT_XHTML) { - thisValue = stripTags(toString()); + if (contentMode == ContentMode.XML || contentMode == ContentMode.XHTML) { + thisValue = stripTags(getStringValue()); } else { - thisValue = toString(); + thisValue = getStringValue(); } if (other instanceof Label - && (((Label) other).getContentMode() == CONTENT_XML - || ((Label) other).getContentMode() == CONTENT_UIDL || ((Label) other) - .getContentMode() == CONTENT_XHTML)) { - otherValue = stripTags(other.toString()); + && (((Label) other).getContentMode() == ContentMode.XML || ((Label) other) + .getContentMode() == ContentMode.XHTML)) { + otherValue = stripTags(((Label) other).getStringValue()); } else { + // TODO not a good idea - and might assume that Field.toString() + // returns a string representation of the value otherValue = other.toString(); } @@ -537,4 +566,8 @@ public class Label extends AbstractComponent implements Property, return res.toString(); } + public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Remove once Vaadin6Component is no longer implemented + } + } diff --git a/src/com/vaadin/ui/Layout.java b/src/com/vaadin/ui/Layout.java index a7cd0abdb4..67bfaa75ff 100644 --- a/src/com/vaadin/ui/Layout.java +++ b/src/com/vaadin/ui/Layout.java @@ -178,15 +178,6 @@ public interface Layout extends ComponentContainer, Serializable { * * @return true if spacing between child components within this layout * is enabled, false otherwise - * @deprecated Use {@link #isSpacing()} instead. - */ - @Deprecated - public boolean isSpacingEnabled(); - - /** - * - * @return true if spacing between child components within this layout - * is enabled, false otherwise */ public boolean isSpacing(); } diff --git a/src/com/vaadin/ui/Link.java b/src/com/vaadin/ui/Link.java index ebea47118a..ed5ffbba3a 100644 --- a/src/com/vaadin/ui/Link.java +++ b/src/com/vaadin/ui/Link.java @@ -4,10 +4,12 @@ package com.vaadin.ui; +import java.util.Map; + import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VLink; +import com.vaadin.terminal.Vaadin6Component; /** * Link is used to create external or internal URL links. @@ -18,17 +20,16 @@ import com.vaadin.terminal.gwt.client.ui.VLink; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VLink.class) -public class Link extends AbstractComponent { +public class Link extends AbstractComponent implements Vaadin6Component { /* Target window border type constant: No window border */ - public static final int TARGET_BORDER_NONE = Window.BORDER_NONE; + public static final int TARGET_BORDER_NONE = Root.BORDER_NONE; /* Target window border type constant: Minimal window border */ - public static final int TARGET_BORDER_MINIMAL = Window.BORDER_MINIMAL; + public static final int TARGET_BORDER_MINIMAL = Root.BORDER_MINIMAL; /* Target window border type constant: Default window border */ - public static final int TARGET_BORDER_DEFAULT = Window.BORDER_DEFAULT; + public static final int TARGET_BORDER_DEFAULT = Root.BORDER_DEFAULT; private Resource resource = null; @@ -94,7 +95,6 @@ public class Link extends AbstractComponent { * @throws PaintException * if the paint operation failed. */ - @Override public void paintContent(PaintTarget target) throws PaintException { if (resource != null) { @@ -232,4 +232,8 @@ public class Link extends AbstractComponent { this.resource = resource; requestRepaint(); } + + public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Remove once Vaadin6Component is no longer implemented + } } diff --git a/src/com/vaadin/ui/ListSelect.java b/src/com/vaadin/ui/ListSelect.java index 5c879f00f5..35ccb34b3c 100644 --- a/src/com/vaadin/ui/ListSelect.java +++ b/src/com/vaadin/ui/ListSelect.java @@ -9,14 +9,12 @@ import java.util.Collection; import com.vaadin.data.Container; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VListSelect; /** * This is a simple list select without, for instance, support for new items, * lazyloading, and other advanced features. */ @SuppressWarnings("serial") -@ClientWidget(VListSelect.class) public class ListSelect extends AbstractSelect { private int columns = 0; diff --git a/src/com/vaadin/ui/LoginForm.java b/src/com/vaadin/ui/LoginForm.java index 80e002435e..1d5203bc6b 100644 --- a/src/com/vaadin/ui/LoginForm.java +++ b/src/com/vaadin/ui/LoginForm.java @@ -4,10 +4,10 @@ package com.vaadin.ui; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; -import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -15,8 +15,9 @@ import java.util.Map; import com.vaadin.Application; import com.vaadin.terminal.ApplicationResource; import com.vaadin.terminal.DownloadStream; -import com.vaadin.terminal.ParameterHandler; -import com.vaadin.terminal.URIHandler; +import com.vaadin.terminal.RequestHandler; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; /** @@ -73,11 +74,20 @@ public class LoginForm extends CustomComponent { } }; - private ParameterHandler paramHandler = new ParameterHandler() { - - public void handleParameters(Map<String, String[]> parameters) { - if (parameters.containsKey("username")) { - getWindow().addURIHandler(uriHandler); + private final RequestHandler requestHandler = new RequestHandler() { + public boolean handleRequest(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException { + String requestPathInfo = request.getRequestPathInfo(); + if ("/loginHandler".equals(requestPathInfo)) { + response.setCacheTime(-1); + response.setContentType("text/html; charset=utf-8"); + response.getWriter() + .write("<html><body>Login form handled." + + "<script type='text/javascript'>top.vaadin.forceSync();" + + "</script></body></html>"); + + Map<String, String[]> parameters = request.getParameterMap(); HashMap<String, String> params = new HashMap<String, String>(); // expecting single params @@ -89,33 +99,12 @@ public class LoginForm extends CustomComponent { } LoginEvent event = new LoginEvent(params); fireEvent(event); + return true; } + return false; } }; - private URIHandler uriHandler = new URIHandler() { - private final String responce = "<html><body>Login form handeled." - + "<script type='text/javascript'>top.vaadin.forceSync();" - + "</script></body></html>"; - - public DownloadStream handleURI(URL context, String relativeUri) { - if (relativeUri != null && relativeUri.contains("loginHandler")) { - if (window != null) { - window.removeURIHandler(this); - } - DownloadStream downloadStream = new DownloadStream( - new ByteArrayInputStream(responce.getBytes()), - "text/html", "loginSuccesfull"); - downloadStream.setCacheTime(-1); - return downloadStream; - } else { - return null; - } - } - }; - - private Window window; - public LoginForm() { iframe.setType(Embedded.TYPE_BROWSER); iframe.setSizeFull(); @@ -132,8 +121,7 @@ public class LoginForm extends CustomComponent { * @return byte array containing login page html */ protected byte[] getLoginHTML() { - String appUri = getApplication().getURL().toString() - + getWindow().getName() + "/"; + String appUri = getApplication().getURL().toString(); try { return ("<!DOCTYPE html PUBLIC \"-//W3C//DTD " @@ -170,11 +158,11 @@ public class LoginForm extends CustomComponent { + "<div>" + usernameCaption + "</div><div >" - + "<input class='v-textfield' style='display:block;' type='text' name='username'></div>" + + "<input class='v-textfield v-connector' style='display:block;' type='text' name='username'></div>" + "<div>" + passwordCaption + "</div>" - + "<div><input class='v-textfield' style='display:block;' type='password' name='password'></div>" + + "<div><input class='v-textfield v-connector' style='display:block;' type='password' name='password'></div>" + "<div><div onclick=\"document.forms[0].submit();\" tabindex=\"0\" class=\"v-button\" role=\"button\" ><span class=\"v-button-wrap\"><span class=\"v-button-caption\">" + loginButtonCaption + "</span></span></div></div></form></div>" + "</body></html>") @@ -188,21 +176,15 @@ public class LoginForm extends CustomComponent { public void attach() { super.attach(); getApplication().addResource(loginPage); - getWindow().addParameterHandler(paramHandler); + getApplication().addRequestHandler(requestHandler); iframe.setSource(loginPage); } @Override public void detach() { getApplication().removeResource(loginPage); - getWindow().removeParameterHandler(paramHandler); - // store window temporary to properly remove uri handler once - // response is handled. (May happen if login handler removes login - // form - window = getWindow(); - if (window.getParent() != null) { - window = window.getParent(); - } + getApplication().removeRequestHandler(requestHandler); + super.detach(); } @@ -281,7 +263,7 @@ public class LoginForm extends CustomComponent { } @Override - public void setWidth(float width, int unit) { + public void setWidth(float width, Unit unit) { super.setWidth(width, unit); if (iframe != null) { if (width < 0) { @@ -293,7 +275,7 @@ public class LoginForm extends CustomComponent { } @Override - public void setHeight(float height, int unit) { + public void setHeight(float height, Unit unit) { super.setHeight(height, unit); if (iframe != null) { if (height < 0) { diff --git a/src/com/vaadin/ui/MenuBar.java b/src/com/vaadin/ui/MenuBar.java index 3469f77ebb..f94bd7ea64 100644 --- a/src/com/vaadin/ui/MenuBar.java +++ b/src/com/vaadin/ui/MenuBar.java @@ -13,8 +13,8 @@ import java.util.Stack; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VMenuBar; -import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.ui.menubar.VMenuBar; /** * <p> @@ -24,8 +24,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * </p> */ @SuppressWarnings("serial") -@ClientWidget(value = VMenuBar.class, loadStyle = LoadStyle.LAZY) -public class MenuBar extends AbstractComponent { +public class MenuBar extends AbstractComponent implements Vaadin6Component { // Items of the top-level menu private final List<MenuItem> menuItems; @@ -33,20 +32,6 @@ public class MenuBar extends AbstractComponent { // Number of items in this menu private int numberOfItems = 0; - /** - * @deprecated - * @see #setCollapse(boolean) - */ - @Deprecated - private boolean collapseItems; - - /** - * @deprecated - * @see #setSubmenuIcon(Resource) - */ - @Deprecated - private Resource submenuIcon; - private MenuItem moreItem; private boolean openRootOnHover; @@ -54,12 +39,7 @@ public class MenuBar extends AbstractComponent { private boolean htmlContentAllowed; /** Paint (serialise) the component for the client. */ - @Override public void paintContent(PaintTarget target) throws PaintException { - - // Superclass writes any common attributes in the paint target. - super.paintContent(target); - target.addAttribute(VMenuBar.OPEN_ROOT_MENU_ON_HOWER, openRootOnHover); if (isHtmlContentAllowed()) { @@ -68,10 +48,6 @@ public class MenuBar extends AbstractComponent { target.startTag("options"); - if (submenuIcon != null) { - target.addAttribute("submenuIcon", submenuIcon); - } - if (getWidth() > -1) { target.startTag("moreItem"); target.addAttribute("text", moreItem.getText()); @@ -103,7 +79,8 @@ public class MenuBar extends AbstractComponent { target.addAttribute("id", item.getId()); if (item.getStyleName() != null) { - target.addAttribute("style", item.getStyleName()); + target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_STYLE, + item.getStyleName()); } if (item.isSeparator()) { @@ -118,16 +95,17 @@ public class MenuBar extends AbstractComponent { Resource icon = item.getIcon(); if (icon != null) { - target.addAttribute("icon", icon); + target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_ICON, icon); } if (!item.isEnabled()) { - target.addAttribute("disabled", true); + target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_DISABLED, true); } String description = item.getDescription(); if (description != null && description.length() > 0) { - target.addAttribute("description", description); + target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_DESCRIPTION, + description); } if (item.isCheckable()) { // if the "checked" attribute is present (either true or false), @@ -147,7 +125,6 @@ public class MenuBar extends AbstractComponent { } /** Deserialize changes received from client. */ - @Override public void changeVariables(Object source, Map<String, Object> variables) { Stack<MenuItem> items = new Stack<MenuItem>(); boolean found = false; @@ -193,7 +170,6 @@ public class MenuBar extends AbstractComponent { */ public MenuBar() { menuItems = new ArrayList<MenuItem>(); - setCollapse(true); setMoreMenuItem(null); } @@ -311,54 +287,6 @@ public class MenuBar extends AbstractComponent { } /** - * Set the icon to be used if a sub-menu has children. Defaults to null; - * - * @param icon - * @deprecated (since 6.2, will be removed in 7.0) Icon is set in theme, no - * need to worry about the visual representation here. - */ - @Deprecated - public void setSubmenuIcon(Resource icon) { - submenuIcon = icon; - requestRepaint(); - } - - /** - * @deprecated - * @see #setSubmenuIcon(Resource) - */ - @Deprecated - public Resource getSubmenuIcon() { - return submenuIcon; - } - - /** - * Enable or disable collapsing top-level items. Top-level items will - * collapse together if there is not enough room for them. Items that don't - * fit will be placed under the "More" menu item. - * - * Collapsing is enabled by default. - * - * @param collapse - * @deprecated (since 6.2, will be removed in 7.0) Collapsing is always - * enabled if the MenuBar has a specified width. - */ - @Deprecated - public void setCollapse(boolean collapse) { - collapseItems = collapse; - requestRepaint(); - } - - /** - * @see #setCollapse(boolean) - * @deprecated - */ - @Deprecated - public boolean getCollapse() { - return collapseItems; - } - - /** * Set the item that is used when collapsing the top level menu. All * "overflowing" items will be added below this. The item command will be * ignored. If set to null, the default item with a downwards arrow is used. @@ -798,8 +726,7 @@ public class MenuBar extends AbstractComponent { /** * Sets the items's description. See {@link #getDescription()} for more * information on what the description is. This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent}. + * {@link RepaintRequestEvent}. * * @param description * the new description string for the component. diff --git a/src/com/vaadin/ui/NativeButton.java b/src/com/vaadin/ui/NativeButton.java index 369b40b93a..6eb4379261 100644 --- a/src/com/vaadin/ui/NativeButton.java +++ b/src/com/vaadin/ui/NativeButton.java @@ -3,11 +3,7 @@ */ package com.vaadin.ui; -import com.vaadin.data.Property; -import com.vaadin.terminal.gwt.client.ui.VNativeButton; - @SuppressWarnings("serial") -@ClientWidget(VNativeButton.class) public class NativeButton extends Button { public NativeButton() { @@ -22,34 +18,4 @@ public class NativeButton extends Button { super(caption, listener); } - public NativeButton(String caption, Object target, String methodName) { - super(caption, target, methodName); - } - - /** - * Creates a new switch button with initial value. - * - * @param state - * the Initial state of the switch-button. - * @param initialState - * @deprecated use the {@link CheckBox} component instead - */ - @Deprecated - public NativeButton(String caption, boolean initialState) { - super(caption, initialState); - } - - /** - * Creates a new switch button that is connected to a boolean property. - * - * @param state - * the Initial state of the switch-button. - * @param dataSource - * @deprecated use the {@link CheckBox} component instead - */ - @Deprecated - public NativeButton(String caption, Property dataSource) { - super(caption, dataSource); - } - -}
\ No newline at end of file +} diff --git a/src/com/vaadin/ui/NativeSelect.java b/src/com/vaadin/ui/NativeSelect.java index e701d212b4..1f85f57c97 100644 --- a/src/com/vaadin/ui/NativeSelect.java +++ b/src/com/vaadin/ui/NativeSelect.java @@ -9,7 +9,6 @@ import java.util.Collection; import com.vaadin.data.Container; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VNativeSelect; /** * This is a simple drop-down select without, for instance, support for @@ -18,7 +17,6 @@ import com.vaadin.terminal.gwt.client.ui.VNativeSelect; * better choice. */ @SuppressWarnings("serial") -@ClientWidget(VNativeSelect.class) public class NativeSelect extends AbstractSelect { // width in characters, mimics TextField diff --git a/src/com/vaadin/ui/Notification.java b/src/com/vaadin/ui/Notification.java new file mode 100644 index 0000000000..bb1f874635 --- /dev/null +++ b/src/com/vaadin/ui/Notification.java @@ -0,0 +1,321 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.io.Serializable; + +import com.vaadin.terminal.Resource; + +/** + * A notification message, used to display temporary messages to the user - for + * example "Document saved", or "Save failed". + * <p> + * 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. + * </p> + * <p> + * 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: + * <ul> + * <li>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.</li> + * <li>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.</li> + * <li>TYPE_ERROR_MESSAGE requires to user to click it before disappearing, and + * can be used for critical messages.</li> + * <li>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.</li> + * </ul> + * </p> + * <p> + * 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. + * </p> + * + */ +public 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; + private boolean htmlContentAllowed; + + /** + * Creates a "humanized" notification message. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption is by + * default rendered as html. + * + * @param caption + * The message to show + */ + public Notification(String caption) { + this(caption, null, TYPE_HUMANIZED_MESSAGE); + } + + /** + * Creates a notification message of the specified type. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption is by + * default rendered as html. + * + * @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. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption and + * description are by default rendered as html. + * + * @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. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption and + * description are by default rendered as html. + * + * @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, description, type, true); + } + + /** + * Creates a notification message of the specified type, with a bigger + * caption and smaller description. + * + * Care should be taken to to avoid XSS vulnerabilities if html is allowed. + * + * @param caption + * The message caption + * @param description + * The message description + * @param type + * The type of message + * @param htmlContentAllowed + * Whether html in the caption and description should be + * displayed as html or as plain text + */ + public Notification(String caption, String description, int type, + boolean htmlContentAllowed) { + this.caption = caption; + this.description = description; + this.htmlContentAllowed = htmlContentAllowed; + 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; + } + + /** + * 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; + } + + /** + * Sets whether html is allowed in the caption and description. If set to + * true, the texts are passed to the browser as html and the developer is + * responsible for ensuring no harmful html is used. If set to false, the + * texts are passed to the browser as plain text. + * + * @param htmlContentAllowed + * true if the texts are used as html, false if used as plain + * text + */ + public void setHtmlContentAllowed(boolean htmlContentAllowed) { + this.htmlContentAllowed = htmlContentAllowed; + } + + /** + * Checks whether caption and description are interpreted as html or plain + * text. + * + * @return true if the texts are used as html, false if used as plain text + * @see #setHtmlContentAllowed(boolean) + */ + public boolean isHtmlContentAllowed() { + return htmlContentAllowed; + } +}
\ No newline at end of file diff --git a/src/com/vaadin/ui/OptionGroup.java b/src/com/vaadin/ui/OptionGroup.java index 884e58824a..a4aaf7ec99 100644 --- a/src/com/vaadin/ui/OptionGroup.java +++ b/src/com/vaadin/ui/OptionGroup.java @@ -17,13 +17,12 @@ import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VOptionGroup; +import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroup; /** * Configures select to be used as an option group. */ @SuppressWarnings("serial") -@ClientWidget(VOptionGroup.class) public class OptionGroup extends AbstractSelect implements FieldEvents.BlurNotifier, FieldEvents.FocusNotifier { @@ -60,7 +59,7 @@ public class OptionGroup extends AbstractSelect implements throws PaintException { super.paintItem(target, itemId); if (!isItemEnabled(itemId)) { - target.addAttribute("disabled", true); + target.addAttribute(VOptionGroup.ATTRIBUTE_OPTION_DISABLED, true); } } diff --git a/src/com/vaadin/ui/OrderedLayout.java b/src/com/vaadin/ui/OrderedLayout.java deleted file mode 100644 index 474fc89867..0000000000 --- a/src/com/vaadin/ui/OrderedLayout.java +++ /dev/null @@ -1,128 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.ui; - -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VOrderedLayout; -import com.vaadin.ui.ClientWidget.LoadStyle; - -/** - * Ordered layout. - * - * <code>OrderedLayout</code> is a component container, which shows the - * subcomponents in the order of their addition in specified orientation. - * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 3.0 - * @deprecated Replaced by VerticalLayout/HorizontalLayout. For type checking - * please not that VerticalLayout/HorizontalLayout do not extend - * OrderedLayout but AbstractOrderedLayout (which also OrderedLayout - * extends). - */ -@SuppressWarnings("serial") -@Deprecated -@ClientWidget(value = VOrderedLayout.class, loadStyle = LoadStyle.EAGER) -public class OrderedLayout extends AbstractOrderedLayout { - /* Predefined orientations */ - - /** - * Components are to be laid out vertically. - */ - public static final int ORIENTATION_VERTICAL = 0; - - /** - * Components are to be laid out horizontally. - */ - public static final int ORIENTATION_HORIZONTAL = 1; - - /** - * Orientation of the layout. - */ - private int orientation; - - /** - * Creates a new ordered layout. The order of the layout is - * <code>ORIENTATION_VERTICAL</code>. - * - * @deprecated Use VerticalLayout instead. - */ - @Deprecated - public OrderedLayout() { - this(ORIENTATION_VERTICAL); - } - - /** - * Create a new ordered layout. The orientation of the layout is given as - * parameters. - * - * @param orientation - * the Orientation of the layout. - * - * @deprecated Use VerticalLayout/HorizontalLayout instead. - */ - @Deprecated - public OrderedLayout(int orientation) { - this.orientation = orientation; - if (orientation == ORIENTATION_VERTICAL) { - setWidth(100, UNITS_PERCENTAGE); - } - } - - /** - * Gets the orientation of the container. - * - * @return the Value of property orientation. - */ - public int getOrientation() { - return orientation; - } - - /** - * Sets the orientation of this OrderedLayout. This method should only be - * used before initial paint. - * - * @param orientation - * the New value of property orientation. - * @deprecated Use VerticalLayout/HorizontalLayout or define orientation in - * constructor instead - */ - @Deprecated - public void setOrientation(int orientation) { - setOrientation(orientation, true); - } - - /** - * Internal method to change orientation of layout. This method should only - * be used before initial paint. - * - * @param orientation - */ - protected void setOrientation(int orientation, boolean needsRepaint) { - // Checks the validity of the argument - if (orientation < ORIENTATION_VERTICAL - || orientation > ORIENTATION_HORIZONTAL) { - throw new IllegalArgumentException(); - } - - this.orientation = orientation; - if (needsRepaint) { - requestRepaint(); - } - } - - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - - // Adds the orientation attributes (the default is vertical) - if (orientation == ORIENTATION_HORIZONTAL) { - target.addAttribute("orientation", "horizontal"); - } - - } - -} diff --git a/src/com/vaadin/ui/Panel.java b/src/com/vaadin/ui/Panel.java index a69413c28b..b2916f78c7 100644 --- a/src/com/vaadin/ui/Panel.java +++ b/src/com/vaadin/ui/Panel.java @@ -15,11 +15,12 @@ import com.vaadin.event.MouseEvents.ClickListener; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Scrollable; +import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VPanel; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.panel.PanelServerRpc; +import com.vaadin.terminal.gwt.client.ui.panel.PanelState; import com.vaadin.ui.Component.Focusable; -import com.vaadin.ui.themes.Reindeer; -import com.vaadin.ui.themes.Runo; /** * Panel - a simple single component container. @@ -30,24 +31,10 @@ import com.vaadin.ui.themes.Runo; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VPanel.class) public class Panel extends AbstractComponentContainer implements Scrollable, ComponentContainer.ComponentAttachListener, - ComponentContainer.ComponentDetachListener, Action.Notifier, Focusable { - - private static final String CLICK_EVENT = VPanel.CLICK_EVENT_IDENTIFIER; - - /** - * Removes extra decorations from the Panel. - * - * @deprecated this style is no longer part of the core framework and this - * component, even though most built-in themes implement this - * style. Use the constant specified in the theme class file - * that you're using, if it provides one, e.g. - * {@link Reindeer#PANEL_LIGHT} or {@link Runo#PANEL_LIGHT} . - */ - @Deprecated - public static final String STYLE_LIGHT = "light"; + ComponentContainer.ComponentDetachListener, Action.Notifier, Focusable, + Vaadin6Component { /** * Content of the panel. @@ -55,32 +42,16 @@ public class Panel extends AbstractComponentContainer implements Scrollable, private ComponentContainer content; /** - * Scroll X position. - */ - private int scrollOffsetX = 0; - - /** - * Scroll Y position. - */ - private int scrollOffsetY = 0; - - /** - * Scrolling mode. - */ - private boolean scrollable = false; - - /** * Keeps track of the Actions added to this component, and manages the * painting and handling as well. */ protected ActionManager actionManager; - /** - * By default the Panel is not in the normal document focus flow and can - * only be focused by using the focus()-method. Change this to 0 if you want - * to have the Panel in the normal focus flow. - */ - private int tabIndex = -1; + private PanelServerRpc rpc = new PanelServerRpc() { + public void click(MouseEventDetails mouseDetails) { + fireEvent(new ClickEvent(Panel.this, mouseDetails)); + } + }; /** * Creates a new empty panel. A VerticalLayout is used as content. @@ -97,8 +68,10 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * the content for the panel. */ public Panel(ComponentContainer content) { + registerRpc(rpc); setContent(content); - setWidth(100, UNITS_PERCENTAGE); + setWidth(100, Unit.PERCENTAGE); + getState().setTabIndex(-1); } /** @@ -139,45 +112,6 @@ public class Panel extends AbstractComponentContainer implements Scrollable, } /** - * Gets the current layout of the panel. - * - * @return the Current layout of the panel. - * @deprecated A Panel can now contain a ComponentContainer which is not - * necessarily a Layout. Use {@link #getContent()} instead. - */ - @Deprecated - public Layout getLayout() { - if (content instanceof Layout) { - return (Layout) content; - } else if (content == null) { - return null; - } - - throw new IllegalStateException( - "Panel does not contain a Layout. Use getContent() instead of getLayout()."); - } - - /** - * Sets the layout of the panel. - * - * If given layout is null, a VerticalLayout with margins set is used as a - * default. - * - * Components from old layout are not moved to new layout by default - * (changed in 5.2.2). Use function in Layout interface manually. - * - * @param newLayout - * the New layout of the panel. - * @deprecated A Panel can now contain a ComponentContainer which is not - * necessarily a Layout. Use - * {@link #setContent(ComponentContainer)} instead. - */ - @Deprecated - public void setLayout(Layout newLayout) { - setContent(newLayout); - } - - /** * Returns the content of the Panel. * * @return @@ -249,20 +183,10 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * (non-Javadoc) * * @see - * com.vaadin.ui.AbstractComponent#paintContent(com.vaadin.terminal.PaintTarget - * ) + * com.vaadin.terminal.Vaadin6Component#paintContent(com.vaadin.terminal + * .PaintTarget) */ - @Override public void paintContent(PaintTarget target) throws PaintException { - content.paint(target); - - target.addVariable(this, "tabindex", getTabIndex()); - - if (isScrollable()) { - target.addVariable(this, "scrollLeft", getScrollLeft()); - target.addVariable(this, "scrollTop", getScrollTop()); - } - if (actionManager != null) { actionManager.paintActions(null, target); } @@ -323,14 +247,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * @see com.vaadin.terminal.VariableOwner#changeVariables(Object, Map) */ @SuppressWarnings("unchecked") - @Override public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); - - if (variables.containsKey(CLICK_EVENT)) { - fireClick((Map<String, Object>) variables.get(CLICK_EVENT)); - } - // Get new size final Integer newWidth = (Integer) variables.get("width"); final Integer newHeight = (Integer) variables.get("height"); @@ -346,11 +263,11 @@ public class Panel extends AbstractComponentContainer implements Scrollable, final Integer newScrollY = (Integer) variables.get("scrollTop"); if (newScrollX != null && newScrollX.intValue() != getScrollLeft()) { // set internally, not to fire request repaint - scrollOffsetX = newScrollX.intValue(); + getState().setScrollLeft(newScrollX.intValue()); } if (newScrollY != null && newScrollY.intValue() != getScrollTop()) { // set internally, not to fire request repaint - scrollOffsetY = newScrollY.intValue(); + getState().setScrollTop(newScrollY.intValue()); } // Actions @@ -368,15 +285,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * @see com.vaadin.terminal.Scrollable#setScrollable(boolean) */ public int getScrollLeft() { - return scrollOffsetX; - } - - /** - * @deprecated use {@link #getScrollLeft()} instead - */ - @Deprecated - public int getScrollOffsetX() { - return getScrollLeft(); + return getState().getScrollLeft(); } /* @@ -385,110 +294,35 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * @see com.vaadin.terminal.Scrollable#setScrollable(boolean) */ public int getScrollTop() { - return scrollOffsetY; - } - - /** - * @deprecated use {@link #getScrollTop()} instead - */ - @Deprecated - public int getScrollOffsetY() { - return getScrollTop(); + return getState().getScrollTop(); } /* * (non-Javadoc) * - * @see com.vaadin.terminal.Scrollable#setScrollable(boolean) - */ - public boolean isScrollable() { - return scrollable; - } - - /** - * Sets the panel as programmatically scrollable. - * - * <p> - * Panel is by default not scrollable programmatically with - * {@link #setScrollLeft(int)} and {@link #setScrollTop(int)}, so if you use - * those methods, you need to enable scrolling with this method. Components - * that extend Panel may have a different default for the programmatic - * scrollability. - * </p> - * - * @see com.vaadin.terminal.Scrollable#setScrollable(boolean) - */ - public void setScrollable(boolean isScrollingEnabled) { - if (scrollable != isScrollingEnabled) { - scrollable = isScrollingEnabled; - requestRepaint(); - } - } - - /** - * Sets the horizontal scroll position. - * - * <p> - * Setting the horizontal scroll position with this method requires that - * programmatic scrolling of the component has been enabled. For Panel it is - * disabled by default, so you have to call {@link #setScrollable(boolean)}. - * Components extending Panel may have a different default for programmatic - * scrollability. - * </p> - * * @see com.vaadin.terminal.Scrollable#setScrollLeft(int) - * @see #setScrollable(boolean) */ - public void setScrollLeft(int pixelsScrolled) { - if (pixelsScrolled < 0) { + public void setScrollLeft(int scrollLeft) { + if (scrollLeft < 0) { throw new IllegalArgumentException( "Scroll offset must be at least 0"); } - if (scrollOffsetX != pixelsScrolled) { - scrollOffsetX = pixelsScrolled; - requestRepaint(); - } - } - - /** - * @deprecated use setScrollLeft() method instead - */ - @Deprecated - public void setScrollOffsetX(int pixels) { - setScrollLeft(pixels); + getState().setScrollLeft(scrollLeft); + requestRepaint(); } - /** - * Sets the vertical scroll position. - * - * <p> - * Setting the vertical scroll position with this method requires that - * programmatic scrolling of the component has been enabled. For Panel it is - * disabled by default, so you have to call {@link #setScrollable(boolean)}. - * Components extending Panel may have a different default for programmatic - * scrollability. - * </p> + /* + * (non-Javadoc) * * @see com.vaadin.terminal.Scrollable#setScrollTop(int) - * @see #setScrollable(boolean) */ - public void setScrollTop(int pixelsScrolledDown) { - if (pixelsScrolledDown < 0) { + public void setScrollTop(int scrollTop) { + if (scrollTop < 0) { throw new IllegalArgumentException( "Scroll offset must be at least 0"); } - if (scrollOffsetY != pixelsScrolledDown) { - scrollOffsetY = pixelsScrolledDown; - requestRepaint(); - } - } - - /** - * @deprecated use setScrollTop() method instead - */ - @Deprecated - public void setScrollOffsetY(int pixels) { - setScrollTop(pixels); + getState().setScrollTop(scrollTop); + requestRepaint(); } /* Documented in superclass */ @@ -526,6 +360,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, */ @Override public void attach() { + getRoot().componentAttached(this); // can't call parent here as this is Panels hierarchy is a hack requestRepaint(); if (content != null) { @@ -544,6 +379,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, if (content != null) { content.detach(); } + getRoot().componentDetached(this); } /** @@ -559,6 +395,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, /* * ACTIONS */ + @Override protected ActionManager getActionManager() { if (actionManager == null) { actionManager = new ActionManager(this); @@ -609,8 +446,8 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * The listener to add */ public void addListener(ClickListener listener) { - addListener(CLICK_EVENT, ClickEvent.class, listener, - ClickListener.clickMethod); + addListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, ClickEvent.class, + listener, ClickListener.clickMethod); } /** @@ -621,33 +458,22 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * The listener to remove */ public void removeListener(ClickListener listener) { - removeListener(CLICK_EVENT, ClickEvent.class, listener); - } - - /** - * Fire a click event to all click listeners. - * - * @param object - * The raw "value" of the variable change from the client side. - */ - private void fireClick(Map<String, Object> parameters) { - MouseEventDetails mouseDetails = MouseEventDetails - .deSerialize((String) parameters.get("mouseDetails")); - fireEvent(new ClickEvent(this, mouseDetails)); + removeListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, + ClickEvent.class, listener); } /** * {@inheritDoc} */ public int getTabIndex() { - return tabIndex; + return getState().getTabIndex(); } /** * {@inheritDoc} */ public void setTabIndex(int tabIndex) { - this.tabIndex = tabIndex; + getState().setTabIndex(tabIndex); requestRepaint(); } @@ -660,4 +486,19 @@ public class Panel extends AbstractComponentContainer implements Scrollable, super.focus(); } + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.ComponentContainer#getComponentCount() + */ + public int getComponentCount() { + // This is so wrong... (#2924) + return content.getComponentCount(); + } + + @Override + public PanelState getState() { + return (PanelState) super.getState(); + } + } diff --git a/src/com/vaadin/ui/PasswordField.java b/src/com/vaadin/ui/PasswordField.java index 99874147d5..c1fccebbfe 100644 --- a/src/com/vaadin/ui/PasswordField.java +++ b/src/com/vaadin/ui/PasswordField.java @@ -4,13 +4,11 @@ package com.vaadin.ui; import com.vaadin.data.Property; -import com.vaadin.terminal.gwt.client.ui.VPasswordField; /** * A field that is used to enter secret text information like passwords. The * entered text is not displayed on the screen. */ -@ClientWidget(VPasswordField.class) public class PasswordField extends AbstractTextField { /** diff --git a/src/com/vaadin/ui/PopupView.java b/src/com/vaadin/ui/PopupView.java index fcad727510..911d926053 100644 --- a/src/com/vaadin/ui/PopupView.java +++ b/src/com/vaadin/ui/PopupView.java @@ -8,9 +8,10 @@ import java.lang.reflect.Method; import java.util.Iterator; import java.util.Map; +import com.vaadin.terminal.LegacyPaint; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VPopupView; +import com.vaadin.terminal.Vaadin6Component; /** * @@ -22,8 +23,8 @@ import com.vaadin.terminal.gwt.client.ui.VPopupView; * @author Vaadin Ltd. */ @SuppressWarnings("serial") -@ClientWidget(VPopupView.class) -public class PopupView extends AbstractComponentContainer { +public class PopupView extends AbstractComponentContainer implements + Vaadin6Component { private Content content; private boolean hideOnMouseOut; @@ -306,11 +307,7 @@ public class PopupView extends AbstractComponentContainer { * * @see com.vaadin.ui.AbstractComponent#paintContent(com.vaadin.terminal.PaintTarget) */ - @Override public void paintContent(PaintTarget target) throws PaintException { - // Superclass writes any common attributes in the paint target. - super.paintContent(target); - String html = content.getMinimizedValueAsHTML(); if (html == null) { html = ""; @@ -321,7 +318,7 @@ public class PopupView extends AbstractComponentContainer { // Only paint component to client if we know that the popup is showing if (isPopupVisible()) { target.startTag("popupComponent"); - visibleComponent.paint(target); + LegacyPaint.paint(visibleComponent, target); target.endTag("popupComponent"); } @@ -334,7 +331,6 @@ public class PopupView extends AbstractComponentContainer { * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, * java.util.Map) */ - @Override public void changeVariables(Object source, Map<String, Object> variables) { if (variables.containsKey("popupVisibility")) { setPopupVisible(((Boolean) variables.get("popupVisibility")) diff --git a/src/com/vaadin/ui/ProgressIndicator.java b/src/com/vaadin/ui/ProgressIndicator.java index 405ff2b52f..4d585cfdd7 100644 --- a/src/com/vaadin/ui/ProgressIndicator.java +++ b/src/com/vaadin/ui/ProgressIndicator.java @@ -4,11 +4,13 @@ package com.vaadin.ui; +import java.util.Map; + import com.vaadin.data.Property; import com.vaadin.data.util.ObjectProperty; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VProgressIndicator; +import com.vaadin.terminal.Vaadin6Component; /** * <code>ProgressIndicator</code> is component that shows user state of a @@ -25,9 +27,8 @@ import com.vaadin.terminal.gwt.client.ui.VProgressIndicator; * @since 4 */ @SuppressWarnings("serial") -@ClientWidget(VProgressIndicator.class) -public class ProgressIndicator extends AbstractField implements Property, - Property.Viewer, Property.ValueChangeListener { +public class ProgressIndicator extends AbstractField<Number> implements + Property.Viewer, Property.ValueChangeListener, Vaadin6Component { /** * Content mode, where the label contains only plain text. The getValue() @@ -110,7 +111,6 @@ public class ProgressIndicator extends AbstractField implements Property, * @throws PaintException * if the Paint Operation fails. */ - @Override public void paintContent(PaintTarget target) throws PaintException { target.addAttribute("indeterminate", indeterminate); target.addAttribute("pollinginterval", pollingInterval); @@ -125,11 +125,12 @@ public class ProgressIndicator extends AbstractField implements Property, * @see com.vaadin.ui.AbstractField#getValue() */ @Override - public Object getValue() { + public Number getValue() { if (dataSource == null) { throw new IllegalStateException("Datasource must be set"); } - return dataSource.getValue(); + // TODO conversions to eliminate cast + return (Number) dataSource.getValue(); } /** @@ -138,7 +139,7 @@ public class ProgressIndicator extends AbstractField implements Property, * * @param newValue * the New value of the ProgressIndicator. - * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object) + * @see com.vaadin.ui.AbstractField#setValue() */ @Override public void setValue(Object newValue) { @@ -149,21 +150,10 @@ public class ProgressIndicator extends AbstractField implements Property, } /** - * @see com.vaadin.ui.AbstractField#toString() - */ - @Override - public String toString() { - if (dataSource == null) { - throw new IllegalStateException("Datasource must be set"); - } - return dataSource.toString(); - } - - /** * @see com.vaadin.ui.AbstractField#getType() */ @Override - public Class<?> getType() { + public Class<? extends Number> getType() { if (dataSource == null) { throw new IllegalStateException("Datasource must be set"); } @@ -257,4 +247,9 @@ public class ProgressIndicator extends AbstractField implements Property, return pollingInterval; } + public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Remove once Vaadin6Component is no longer implemented + + } + } diff --git a/src/com/vaadin/ui/RichTextArea.java b/src/com/vaadin/ui/RichTextArea.java index f4d88edc78..16d4761b40 100644 --- a/src/com/vaadin/ui/RichTextArea.java +++ b/src/com/vaadin/ui/RichTextArea.java @@ -10,8 +10,7 @@ import java.util.Map; import com.vaadin.data.Property; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; -import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.terminal.Vaadin6Component; /** * A simple RichTextArea to edit HTML format text. @@ -20,8 +19,8 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * {@link RichTextArea} may produce unexpected results as formatting is counted * into length of field. */ -@ClientWidget(value = VRichTextArea.class, loadStyle = LoadStyle.LAZY) -public class RichTextArea extends AbstractField { +public class RichTextArea extends AbstractField<String> implements + Vaadin6Component { /** * Value formatter used to format the string contents. @@ -104,7 +103,6 @@ public class RichTextArea extends AbstractField { setCaption(caption); } - @Override public void paintContent(PaintTarget target) throws PaintException { if (selectAll) { target.addAttribute("selectAll", true); @@ -122,13 +120,13 @@ public class RichTextArea extends AbstractField { } target.addVariable(this, "text", value); - super.paintContent(target); } @Override public void setReadOnly(boolean readOnly) { super.setReadOnly(readOnly); // IE6 cannot support multi-classname selectors properly + // TODO Can be optimized now that support for I6 is dropped if (readOnly) { addStyleName("v-richtextarea-readonly"); } else { @@ -175,8 +173,8 @@ public class RichTextArea extends AbstractField { } @Override - public Object getValue() { - Object v = super.getValue(); + public String getValue() { + String v = super.getValue(); if (format == null || v == null) { return v; } @@ -187,11 +185,7 @@ public class RichTextArea extends AbstractField { } } - @Override public void changeVariables(Object source, Map<String, Object> variables) { - - super.changeVariables(source, variables); - // Sets the text if (variables.containsKey("text") && !isReadOnly()) { @@ -221,7 +215,7 @@ public class RichTextArea extends AbstractField { } @Override - public Class getType() { + public Class<String> getType() { return String.class; } @@ -342,7 +336,7 @@ public class RichTextArea extends AbstractField { @Override protected boolean isEmpty() { - return super.isEmpty() || toString().length() == 0; + return super.isEmpty() || getValue().length() == 0; } } diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java new file mode 100644 index 0000000000..405ae8da93 --- /dev/null +++ b/src/com/vaadin/ui/Root.java @@ -0,0 +1,1590 @@ +/* +@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. + * <p> + * 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. + * </p> + * <p> + * 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. + * </p> + * <p> + * 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)}. + * </p> + * <p> + * If a {@link EagerInit} annotation is present on a class extending + * <code>Root</code>, 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. + * </p> + * + * @see #init(WrappedRequest) + * @see Application#getRoot(WrappedRequest) + * + * @since 7.0 + */ +public abstract class Root extends AbstractComponentContainer implements + Action.Container, Action.Notifier, Vaadin6Component { + + /** + * Listener that gets notified when the size of the browser window + * containing the root has changed. + * + * @see Root#addListener(BrowserWindowResizeListener) + */ + public interface BrowserWindowResizeListener extends Serializable { + /** + * Invoked when the browser window containing a Root has been resized. + * + * @param event + * a browser window resize event + */ + public void browserWindowResized(BrowserWindowResizeEvent event); + } + + /** + * Event that is fired when a browser window containing a root is resized. + */ + public class BrowserWindowResizeEvent extends Component.Event { + + private final int width; + private final int height; + + /** + * Creates a new event + * + * @param source + * the root for which the browser window has been resized + * @param width + * the new width of the browser window + * @param height + * the new height of the browser window + */ + public BrowserWindowResizeEvent(Root source, int width, int height) { + super(source); + this.width = width; + this.height = height; + } + + @Override + public Root getSource() { + return (Root) super.getSource(); + } + + /** + * Gets the new browser window height + * + * @return an integer with the new pixel height of the browser window + */ + public int getHeight() { + return height; + } + + /** + * Gets the new browser window width + * + * @return an integer with the new pixel width of the browser window + */ + public int getWidth() { + return width; + } + } + + private static final Method BROWSWER_RESIZE_METHOD = ReflectTools + .findMethod(BrowserWindowResizeListener.class, + "browserWindowResized", BrowserWindowResizeEvent.class); + + /** + * Listener that listens changes in URI fragment. + */ + public interface FragmentChangedListener extends Serializable { + public void fragmentChanged(FragmentChangedEvent event); + } + + /** + * Event fired when uri fragment changes. + */ + public class FragmentChangedEvent extends Component.Event { + + /** + * The new uri fragment + */ + private final String fragment; + + /** + * Creates a new instance of UriFragmentReader change event. + * + * @param source + * the Source of the event. + */ + public FragmentChangedEvent(Root source, String fragment) { + super(source); + this.fragment = fragment; + } + + /** + * Gets the root in which the fragment has changed. + * + * @return the root in which the fragment has changed + */ + public Root getRoot() { + return (Root) getComponent(); + } + + /** + * Get the new fragment + * + * @return the new fragment + */ + public String getFragment() { + return fragment; + } + } + + /** + * Helper class to emulate the main window from Vaadin 6 using roots. This + * class should be used in the same way as Window used as a browser level + * window in Vaadin 6 with {@link com.vaadin.Application.LegacyApplication} + */ + @Deprecated + @EagerInit + public static class LegacyWindow extends Root { + private String name; + + /** + * Create a new legacy window + */ + public LegacyWindow() { + super(); + } + + /** + * Creates a new legacy window with the given caption + * + * @param caption + * the caption of the window + */ + public LegacyWindow(String caption) { + super(caption); + } + + /** + * Creates a legacy window with the given caption and content layout + * + * @param caption + * @param content + */ + public LegacyWindow(String caption, ComponentContainer content) { + super(caption, content); + } + + @Override + protected void init(WrappedRequest request) { + // Just empty + } + + /** + * Gets the unique name of the window. The name of the window is used to + * uniquely identify it. + * <p> + * 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. + * </p> + * <p> + * Note! Portlets do not support direct window access through URLs. + * </p> + * + * @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. + * <p> + * 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. + * </p> + * <p> + * This method can only be called before the window is added to an + * application. + * <p> + * Note! Portlets do not support direct window access through URLs. + * </p> + * + * @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. + * <p> + * Note! This method can not be used for portlets. + * </p> + * + * @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 List<Notification> notifications; + + /** + * 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 List<String> jsExecQueue = null; + + /** + * List of windows in this root. + */ + private final LinkedHashSet<Window> windows = new LinkedHashSet<Window>(); + + /** + * Resources to be opened automatically on next repaint. The list is + * automatically cleared when it has been sent to the client. + */ + private final LinkedList<OpenResource> openList = new LinkedList<OpenResource>(); + + /** + * The component that should be scrolled into view after the next repaint. + * Null if nothing should be scrolled into view. + */ + private Component scrollIntoView; + + /** + * The id of this root, used to find the server side instance of the root + * form which a request originates. A negative value indicates that the root + * id has not yet been assigned by the Application. + * + * @see Application#nextRootId + */ + private int rootId = -1; + + /** + * Keeps track of the Actions added to this component, and manages the + * painting and handling as well. + */ + protected ActionManager actionManager; + + /** + * Thread local for keeping track of the current root. + */ + private static final ThreadLocal<Root> currentRoot = new ThreadLocal<Root>(); + + private int browserWindowWidth = -1; + private int browserWindowHeight = -1; + + /** Identifies the click event */ + private static final String CLICK_EVENT_ID = VRoot.CLICK_EVENT_ID; + + private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( + this); + + private RootServerRpc rpc = new RootServerRpc() { + public void click(MouseEventDetails mouseDetails) { + fireEvent(new ClickEvent(Root.this, mouseDetails)); + } + }; + + /** + * Creates a new empty root without a caption. This root will have a + * {@link VerticalLayout} with margins enabled as its content. + */ + public Root() { + this((ComponentContainer) null); + } + + /** + * Creates a new root with the given component container as its content. + * + * @param content + * the content container to use as this roots content. + * + * @see #setContent(ComponentContainer) + */ + public Root(ComponentContainer content) { + registerRpc(rpc); + setSizeFull(); + setContent(content); + } + + /** + * Creates a new empty root with the given caption. This root will have a + * {@link VerticalLayout} with margins enabled as its content. + * + * @param caption + * the caption of the root, used as the page title if there's + * nothing but the application on the web page + * + * @see #setCaption(String) + */ + public Root(String caption) { + this((ComponentContainer) null); + setCaption(caption); + } + + /** + * Creates a new root with the given caption and content. + * + * @param caption + * the caption of the root, used as the page title if there's + * nothing but the application on the web page + * @param content + * the content container to use as this roots content. + * + * @see #setContent(ComponentContainer) + * @see #setCaption(String) + */ + public Root(String caption, ComponentContainer content) { + this(content); + setCaption(caption); + } + + @Override + public RootState getState() { + return (RootState) super.getState(); + } + + @Override + protected ComponentState createState() { + // This is a workaround for a problem with creating the correct state + // object during build + return new RootState(); + } + + /** + * Overridden to return a value instead of referring to the parent. + * + * @return this root + * + * @see com.vaadin.ui.AbstractComponent#getRoot() + */ + @Override + public Root getRoot() { + return this; + } + + public void replaceComponent(Component oldComponent, Component newComponent) { + throw new UnsupportedOperationException(); + } + + @Override + public Application getApplication() { + return application; + } + + public void paintContent(PaintTarget target) throws PaintException { + // Open requested resource + synchronized (openList) { + if (!openList.isEmpty()) { + for (final Iterator<OpenResource> i = openList.iterator(); i + .hasNext();) { + (i.next()).paintContent(target); + } + openList.clear(); + } + } + + // Paint notifications + if (notifications != null) { + target.startTag("notifications"); + for (final Iterator<Notification> it = notifications.iterator(); it + .hasNext();) { + final Notification n = it.next(); + target.startTag("notification"); + if (n.getCaption() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_CAPTION, + n.getCaption()); + } + if (n.getDescription() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_MESSAGE, + n.getDescription()); + } + if (n.getIcon() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_ICON, + n.getIcon()); + } + if (!n.isHtmlContentAllowed()) { + target.addAttribute( + VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED, true); + } + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_POSITION, + n.getPosition()); + target.addAttribute(VNotification.ATTRIBUTE_NOTIFICATION_DELAY, + n.getDelayMsec()); + if (n.getStyleName() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_STYLE, + n.getStyleName()); + } + target.endTag("notification"); + } + target.endTag("notifications"); + notifications = null; + } + + // Add executable javascripts if needed + if (jsExecQueue != null) { + for (String script : jsExecQueue) { + target.startTag("execJS"); + target.addAttribute("script", script); + target.endTag("execJS"); + } + jsExecQueue = null; + } + + if (scrollIntoView != null) { + target.addAttribute("scrollTo", scrollIntoView); + scrollIntoView = null; + } + + if (pendingFocus != null) { + // ensure focused component is still attached to this main window + if (pendingFocus.getRoot() == this + || (pendingFocus.getRoot() != null && pendingFocus + .getRoot().getParent() == this)) { + target.addAttribute("focused", pendingFocus); + } + pendingFocus = null; + } + + if (actionManager != null) { + actionManager.paintActions(null, target); + } + + if (fragment != null) { + target.addAttribute(VRoot.FRAGMENT_VARIABLE, fragment); + } + + if (isResizeLazy()) { + target.addAttribute(VRoot.RESIZE_LAZY, true); + } + } + + /** + * Fire a click event to all click listeners. + * + * @param object + * The raw "value" of the variable change from the client side. + */ + private void fireClick(Map<String, Object> parameters) { + MouseEventDetails mouseDetails = MouseEventDetails + .deSerialize((String) parameters.get("mouseDetails")); + fireEvent(new ClickEvent(this, mouseDetails)); + } + + @SuppressWarnings("unchecked") + public void changeVariables(Object source, Map<String, Object> variables) { + if (variables.containsKey(CLICK_EVENT_ID)) { + fireClick((Map<String, Object>) variables.get(CLICK_EVENT_ID)); + } + + // Actions + if (actionManager != null) { + actionManager.handleActions(variables, this); + } + + if (variables.containsKey(VRoot.FRAGMENT_VARIABLE)) { + String fragment = (String) variables.get(VRoot.FRAGMENT_VARIABLE); + setFragment(fragment, true); + } + + boolean sendResizeEvent = false; + if (variables.containsKey("height")) { + browserWindowHeight = ((Integer) variables.get("height")) + .intValue(); + sendResizeEvent = true; + } + if (variables.containsKey("width")) { + browserWindowWidth = ((Integer) variables.get("width")).intValue(); + sendResizeEvent = true; + } + if (sendResizeEvent) { + fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth, + browserWindowHeight)); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.ComponentContainer#getComponentIterator() + */ + public Iterator<Component> getComponentIterator() { + return Collections.singleton((Component) getContent()).iterator(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.ComponentContainer#getComponentCount() + */ + public int getComponentCount() { + return getContent() == null ? 0 : 1; + } + + /** + * Sets the application to which this root is assigned. It is not legal to + * change the application once it has been set nor to set a + * <code>null</code> application. + * <p> + * This method is mainly intended for internal use by the framework. + * </p> + * + * @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. + * <p> + * This method is mainly intended for internal use by the framework. + * </p> + * + * @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 given <code>Window</code> is <code>null</code>. + */ + 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<Window> getWindows() { + return Collections.unmodifiableCollection(windows); + } + + @Override + public void focus() { + super.focus(); + } + + /** + * Component that should be focused after the next repaint. Null if no focus + * change should take place. + */ + private Focusable pendingFocus; + + /** + * The current URI fragment. + */ + private String fragment; + + private boolean resizeLazy = false; + + /** + * 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 + */ + public void setFocusedComponent(Focusable focusable) { + pendingFocus = focusable; + requestRepaint(); + } + + /** + * Shows a notification message on the middle of the root. The message + * automatically disappears ("humanized message"). + * + * Care should be taken to to avoid XSS vulnerabilities as the caption is + * rendered as html. + * + * @see #showNotification(Notification) + * @see Notification + * + * @param caption + * The message + */ + public void showNotification(String caption) { + addNotification(new Notification(caption)); + } + + /** + * Shows a notification message the root. 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. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption is + * rendered as html. + * + * @see #showNotification(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 root. The message automatically + * disappears ("humanized message"). + * + * Care should be taken to to avoid XSS vulnerabilities as the caption and + * description are rendered as html. + * + * @see #showNotification(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. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption and + * description are rendered as html. + * + * @see #showNotification(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 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. + * + * Care should be taken to avoid XSS vulnerabilities if html content is + * allowed. + * + * @see #showNotification(Notification) + * @see Notification + * + * @param caption + * The message caption + * @param description + * The message description + * @param type + * The type of message + * @param htmlContentAllowed + * Whether html in the caption and description should be + * displayed as html or as plain text + */ + public void showNotification(String caption, String description, int type, + boolean htmlContentAllowed) { + addNotification(new Notification(caption, description, type, + htmlContentAllowed)); + } + + /** + * 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); + } + + /** + * Internal helper method to actually add a notification. + * + * @param notification + * the notification to add + */ + private void addNotification(Notification notification) { + if (notifications == null) { + notifications = new LinkedList<Notification>(); + } + notifications.add(notification); + requestRepaint(); + } + + /** + * Executes JavaScript in this root. + * + * <p> + * 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. + * </p> + * + * <p> + * 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. + * </p> + * + * @param script + * JavaScript snippet that will be executed. + */ + public void executeJavaScript(String script) { + if (jsExecQueue == null) { + jsExecQueue = new ArrayList<String>(); + } + + jsExecQueue.add(script); + + requestRepaint(); + } + + /** + * Scrolls any component between the component and root to a suitable + * position so the component is visible to the user. The given component + * must belong to this root. + * + * @param component + * the component to be scrolled into view + * @throws IllegalArgumentException + * if {@code component} does not belong to this root + */ + public void scrollIntoView(Component component) + throws IllegalArgumentException { + if (component.getRoot() != this) { + throw new IllegalArgumentException( + "The component where to scroll must belong to this root."); + } + scrollIntoView = component; + requestRepaint(); + } + + /** + * Gets the content of this root. The content is a component container that + * serves as the outermost item of the visual contents of this root. + * + * @return a component container to use as content + * + * @see #setContent(ComponentContainer) + * @see #createDefaultLayout() + */ + public ComponentContainer getContent() { + return (ComponentContainer) getState().getContent(); + } + + /** + * Helper method to create the default content layout that is used if no + * content has not been explicitly defined. + * + * @return a newly created layout + */ + private static VerticalLayout createDefaultLayout() { + VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + return layout; + } + + /** + * Sets the content of this root. The content is a component container that + * serves as the outermost item of the visual contents of this root. If no + * content has been set, a {@link VerticalLayout} with margins enabled will + * be used by default - see {@link #createDefaultLayout()}. The content can + * also be set in a constructor. + * + * @return a component container to use as content + * + * @see #Root(ComponentContainer) + * @see #createDefaultLayout() + */ + public void setContent(ComponentContainer content) { + if (content == null) { + content = createDefaultLayout(); + } + + if (getState().getContent() != null) { + super.removeComponent((Component) getState().getContent()); + } + getState().setContent(content); + if (content != null) { + super.addComponent(content); + } + } + + /** + * Adds a component to this root. The component is not added directly to the + * root, but instead to the content container ({@link #getContent()}). + * + * @param component + * the component to add to this root + * + * @see #getContent() + */ + @Override + public void addComponent(Component component) { + getContent().addComponent(component); + } + + /** + * This implementation removes the component from the content container ( + * {@link #getContent()}) instead of from the actual root. + */ + @Override + public void removeComponent(Component component) { + getContent().removeComponent(component); + } + + /** + * This implementation removes the components from the content container ( + * {@link #getContent()}) instead of from the actual root. + */ + @Override + public void removeAllComponents() { + getContent().removeAllComponents(); + } + + /** + * Internal initialization method, should not be overridden. This method is + * not declared as final because that would break compatibility with e.g. + * CDI. + * + * @param request + * the initialization request + */ + public void doInit(WrappedRequest request) { + BrowserDetails browserDetails = request.getBrowserDetails(); + if (browserDetails != null) { + fragment = browserDetails.getUriFragment(); + } + + // Call the init overridden by the application developer + init(request); + } + + /** + * Initializes this root. This method is intended to be overridden by + * subclasses to build the view and configure non-component functionality. + * Performing the initialization in a constructor is not suggested as the + * state of the root is not properly set up when the constructor is invoked. + * <p> + * 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. + * </p> + * + * @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. + * <p> + * 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. + * </p> + * + * @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 + * <code>null</code> + * + * @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. + * <p> + * 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 + * <code>null</code> window name is also a special case. + * </p> + * <p> + * "", 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. + * </p> + * <p> + * "_blank" as {@code windowName} causes the resource to always be opened in + * a new window or tab (depends on the browser and browser settings). + * </p> + * <p> + * "_top" and "_parent" as {@code windowName} works as specified by the HTML + * standard. + * </p> + * <p> + * 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. + * </p> + * + * @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 <T extends Action & com.vaadin.event.Action.Listener> void addAction( + T action) { + getActionManager().addAction(action); + } + + public <T extends Action & com.vaadin.event.Action.Listener> void removeAction( + T action) { + if (actionManager != null) { + actionManager.removeAction(action); + } + } + + public void addActionHandler(Handler actionHandler) { + getActionManager().addActionHandler(actionHandler); + } + + public void removeActionHandler(Handler actionHandler) { + if (actionManager != null) { + actionManager.removeActionHandler(actionHandler); + } + } + + /** + * Should resize operations be lazy, i.e. should there be a delay before + * layout sizes are recalculated. Speeds up resize operations in slow UIs + * with the penalty of slightly decreased usability. + * <p> + * Default value: <code>false</code> + * + * @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 <code>true</code> if lazy resize is enabled, <code>false</code> + * 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. + * <p> + * 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); + } + +} diff --git a/src/com/vaadin/ui/Select.java b/src/com/vaadin/ui/Select.java index 0ea331dc40..5398f11391 100644 --- a/src/com/vaadin/ui/Select.java +++ b/src/com/vaadin/ui/Select.java @@ -23,7 +23,6 @@ import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VFilterSelect; /** * <p> @@ -44,7 +43,6 @@ import com.vaadin.terminal.gwt.client.ui.VFilterSelect; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VFilterSelect.class) public class Select extends AbstractSelect implements AbstractSelect.Filtering, FieldEvents.BlurNotifier, FieldEvents.FocusNotifier { @@ -147,11 +145,6 @@ public class Select extends AbstractSelect implements AbstractSelect.Filtering, target.addAttribute("modified", true); } - // Adds the required attribute - if (!isReadOnly() && isRequired()) { - target.addAttribute("required", true); - } - if (isNewItemsAllowed()) { target.addAttribute("allownewitem", true); } @@ -271,11 +264,6 @@ public class Select extends AbstractSelect implements AbstractSelect.Filtering, currentPage = -1; // current page is always set by client optionRequest = true; - - // Hide the error indicator if needed - if (shouldHideErrors()) { - target.addAttribute("hideErrors", true); - } } /** @@ -754,11 +742,15 @@ public class Select extends AbstractSelect implements AbstractSelect.Filtering, * @deprecated use {@link ListSelect}, {@link OptionGroup} or * {@link TwinColSelect} instead * @see com.vaadin.ui.AbstractSelect#setMultiSelect(boolean) + * @throws UnsupportedOperationException + * if trying to activate multiselect mode */ @Deprecated @Override public void setMultiSelect(boolean multiSelect) { - super.setMultiSelect(multiSelect); + if (multiSelect) { + throw new UnsupportedOperationException("Multiselect not supported"); + } } /** diff --git a/src/com/vaadin/ui/Slider.java b/src/com/vaadin/ui/Slider.java index 1d67a6b12c..dc5dc0be98 100644 --- a/src/com/vaadin/ui/Slider.java +++ b/src/com/vaadin/ui/Slider.java @@ -8,7 +8,7 @@ import java.util.Map; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VSlider; +import com.vaadin.terminal.Vaadin6Component; /** * A component for selecting a numerical value within a range. @@ -46,22 +46,12 @@ import com.vaadin.terminal.gwt.client.ui.VSlider; * * @author Vaadin Ltd. */ -@ClientWidget(VSlider.class) -public class Slider extends AbstractField { +public class Slider extends AbstractField<Double> implements Vaadin6Component { public static final int ORIENTATION_HORIZONTAL = 0; public static final int ORIENTATION_VERTICAL = 1; - /** - * Style constant representing a scrollbar styled slider. Use this with - * {@link #addStyleName(String)}. Default styling usually represents a - * common slider found e.g. in Adobe Photoshop. The client side - * implementation dictates how different styles will look. - */ - @Deprecated - public static final String STYLE_SCROLLBAR = "scrollbar"; - /** Minimum value of slider */ private double min = 0; @@ -80,35 +70,6 @@ public class Slider extends AbstractField { private int orientation = ORIENTATION_HORIZONTAL; /** - * Slider size in pixels. In horizontal mode, if set to -1, allow 100% width - * of container. In vertical mode, if set to -1, default height is - * determined by the client-side implementation. - * - * @deprecated - */ - @Deprecated - private int size = -1; - - /** - * Handle (draggable control element) size in percents relative to base - * size. Must be a value between 1-99. Other values are converted to nearest - * bound. A negative value sets the width to auto (client-side - * implementation calculates). - * - * @deprecated The size is dictated by the current theme. - */ - @Deprecated - private int handleSize = -1; - - /** - * Show arrows that can be pressed to slide the handle in some increments - * (client-side implementation decides the increment, usually somewhere - * between 5-10% of slide range). - */ - @Deprecated - private final boolean arrows = false; - - /** * Default slider constructor. Sets all values to defaults and the slide * handle at minimum value. * @@ -198,17 +159,8 @@ public class Slider extends AbstractField { */ public void setMax(double max) { this.max = max; - try { - if ((new Double(getValue().toString())).doubleValue() > max) { - super.setValue(new Double(max)); - } - } catch (final ClassCastException e) { - // FIXME: Handle exception - /* - * Where does ClassCastException come from? Can't see any casts - * above - */ - super.setValue(new Double(max)); + if (getValue() > max) { + setValue(max); } requestRepaint(); } @@ -231,17 +183,8 @@ public class Slider extends AbstractField { */ public void setMin(double min) { this.min = min; - try { - if ((new Double(getValue().toString())).doubleValue() < min) { - super.setValue(new Double(min)); - } - } catch (final ClassCastException e) { - // FIXME: Handle exception - /* - * Where does ClassCastException come from? Can't see any casts - * above - */ - super.setValue(new Double(min)); + if (getValue() < min) { + setValue(min); } requestRepaint(); } @@ -303,8 +246,8 @@ public class Slider extends AbstractField { * If the given value is not inside the range of the slider. * @see #setMin(double) {@link #setMax(double)} */ - public void setValue(Double value, boolean repaintIsNotNeeded) - throws ValueOutOfBoundsException { + @Override + protected void setValue(Double value, boolean repaintIsNotNeeded) { final double v = value.doubleValue(); double newValue; if (resolution > 0) { @@ -320,102 +263,22 @@ public class Slider extends AbstractField { throw new ValueOutOfBoundsException(value); } } - super.setValue(new Double(newValue), repaintIsNotNeeded); - } - - /** - * Sets the value of the slider. - * - * @param value - * The new value of the slider. - * @throws ValueOutOfBoundsException - * If the given value is not inside the range of the slider. - * @see #setMin(double) {@link #setMax(double)} - */ - public void setValue(Double value) throws ValueOutOfBoundsException { - setValue(value, false); - } - - /** - * Sets the value of the slider. - * - * @param value - * The new value of the slider. - * @throws ValueOutOfBoundsException - * If the given value is not inside the range of the slider. - * @see #setMin(double) {@link #setMax(double)} - */ - public void setValue(double value) throws ValueOutOfBoundsException { - setValue(new Double(value), false); - } - - /** - * Get the current slider size. - * - * @return size in pixels or -1 for auto sizing. - * @deprecated use standard getWidth/getHeight instead - */ - @Deprecated - public int getSize() { - return size; + super.setValue(newValue, repaintIsNotNeeded); } - /** - * Set the size for this slider. - * - * @param size - * in pixels, or -1 auto sizing. - * @deprecated use standard setWidth/setHeight instead - */ - @Deprecated - public void setSize(int size) { - this.size = size; - switch (orientation) { - case ORIENTATION_HORIZONTAL: - setWidth(size, UNITS_PIXELS); - break; - default: - setHeight(size, UNITS_PIXELS); - break; + @Override + public void setValue(Object newFieldValue) + throws com.vaadin.data.Property.ReadOnlyException { + if (newFieldValue != null && newFieldValue instanceof Number + && !(newFieldValue instanceof Double)) { + // Support setting all types of Numbers + newFieldValue = ((Number) newFieldValue).doubleValue(); } - requestRepaint(); - } - /** - * Get the handle size of this slider. - * - * @return handle size in percentages. - * @deprecated The size is dictated by the current theme. - */ - @Deprecated - public int getHandleSize() { - return handleSize; + super.setValue(newFieldValue); } - /** - * Set the handle size of this slider. - * - * @param handleSize - * in percentages relative to slider base size. - * @deprecated The size is dictated by the current theme. - */ - @Deprecated - public void setHandleSize(int handleSize) { - if (handleSize < 0) { - this.handleSize = -1; - } else if (handleSize > 99) { - this.handleSize = 99; - } else if (handleSize < 1) { - this.handleSize = 1; - } else { - this.handleSize = handleSize; - } - requestRepaint(); - } - - @Override public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); target.addAttribute("min", min); if (max > min) { @@ -426,30 +289,15 @@ public class Slider extends AbstractField { target.addAttribute("resolution", resolution); if (resolution > 0) { - target.addVariable(this, "value", - ((Double) getValue()).doubleValue()); + target.addVariable(this, "value", getValue().doubleValue()); } else { - target.addVariable(this, "value", ((Double) getValue()).intValue()); + target.addVariable(this, "value", getValue().intValue()); } if (orientation == ORIENTATION_VERTICAL) { target.addAttribute("vertical", true); } - if (arrows) { - target.addAttribute("arrows", true); - } - - if (size > -1) { - target.addAttribute("size", size); - } - - if (min != max && min < max) { - target.addAttribute("hsize", handleSize); - } else { - target.addAttribute("hsize", 100); - } - } /** @@ -459,9 +307,7 @@ public class Slider extends AbstractField { * @param source * @param variables */ - @Override public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); if (variables.containsKey("value")) { final Object value = variables.get("value"); final Double newValue = new Double(value.toString()); @@ -491,7 +337,7 @@ public class Slider extends AbstractField { * @author Vaadin Ltd. * */ - public class ValueOutOfBoundsException extends Exception { + public class ValueOutOfBoundsException extends RuntimeException { private final Double value; @@ -517,7 +363,7 @@ public class Slider extends AbstractField { } @Override - public Class getType() { + public Class<Double> getType() { return Double.class; } diff --git a/src/com/vaadin/ui/SplitPanel.java b/src/com/vaadin/ui/SplitPanel.java deleted file mode 100644 index bae1bf7ce0..0000000000 --- a/src/com/vaadin/ui/SplitPanel.java +++ /dev/null @@ -1,114 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.ui; - -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VSplitPanelHorizontal; -import com.vaadin.ui.ClientWidget.LoadStyle; - -/** - * SplitPanel. - * - * <code>SplitPanel</code> is a component container, that can contain two - * components (possibly containers) which are split by divider element. - * - * @author Vaadin Ltd. - * @version - * @VERSION@ - * @since 5.0 - * @deprecated in 6.5. Use {@link HorizontalSplitPanel} or - * {@link VerticalSplitPanel} instead. - */ -@Deprecated -@ClientWidget(value = VSplitPanelHorizontal.class, loadStyle = LoadStyle.EAGER) -public class SplitPanel extends AbstractSplitPanel { - - /* Predefined orientations */ - - /** - * Components are to be laid out vertically. - */ - public static final int ORIENTATION_VERTICAL = 0; - - /** - * Components are to be laid out horizontally. - */ - public static final int ORIENTATION_HORIZONTAL = 1; - - /** - * Orientation of the layout. - */ - private int orientation; - - /** - * Creates a new split panel. The orientation of the panels is - * <code>ORIENTATION_VERTICAL</code>. - */ - public SplitPanel() { - super(); - orientation = ORIENTATION_VERTICAL; - setSizeFull(); - } - - /** - * Create a new split panels. The orientation of the panel is given as - * parameters. - * - * @param orientation - * the Orientation of the layout. - */ - public SplitPanel(int orientation) { - this(); - setOrientation(orientation); - } - - /** - * Paints the content of this component. - * - * @param target - * the Paint Event. - * @throws PaintException - * if the paint operation failed. - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - - if (orientation == ORIENTATION_VERTICAL) { - target.addAttribute("vertical", true); - } - - } - - /** - * Gets the orientation of the split panel. - * - * @return the Value of property orientation. - * - */ - public int getOrientation() { - return orientation; - } - - /** - * Sets the orientation of the split panel. - * - * @param orientation - * the New value of property orientation. - */ - public void setOrientation(int orientation) { - - // Checks the validity of the argument - if (orientation < ORIENTATION_VERTICAL - || orientation > ORIENTATION_HORIZONTAL) { - throw new IllegalArgumentException(); - } - - this.orientation = orientation; - requestRepaint(); - } - -} diff --git a/src/com/vaadin/ui/TabSheet.java b/src/com/vaadin/ui/TabSheet.java index 09d1002b48..23dee15359 100644 --- a/src/com/vaadin/ui/TabSheet.java +++ b/src/com/vaadin/ui/TabSheet.java @@ -7,10 +7,8 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -22,11 +20,13 @@ import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.event.FieldEvents.FocusNotifier; import com.vaadin.terminal.ErrorMessage; import com.vaadin.terminal.KeyMapper; +import com.vaadin.terminal.LegacyPaint; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VTabsheet; -import com.vaadin.terminal.gwt.server.CommunicationManager; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.ui.tabsheet.TabsheetBaseConnector; +import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheet; import com.vaadin.ui.Component.Focusable; import com.vaadin.ui.themes.Reindeer; import com.vaadin.ui.themes.Runo; @@ -60,10 +60,8 @@ import com.vaadin.ui.themes.Runo; * @VERSION@ * @since 3.0 */ -@SuppressWarnings("serial") -@ClientWidget(VTabsheet.class) public class TabSheet extends AbstractComponentContainer implements Focusable, - FocusNotifier, BlurNotifier { + FocusNotifier, BlurNotifier, Vaadin6Component { /** * List of component tabs (tab contents). In addition to being on this list, @@ -86,7 +84,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * Mapper between server-side component instances (tab contents) and keys * given to the client that identify tabs. */ - private final KeyMapper keyMapper = new KeyMapper(); + private final KeyMapper<Component> keyMapper = new KeyMapper<Component>(); /** * When true, the tab selection area is not displayed to the user. @@ -94,11 +92,6 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, private boolean tabsHidden; /** - * Tabs that have been shown to the user (have been painted as selected). - */ - private HashSet<Component> paintedTabs = new HashSet<Component>(); - - /** * Handler to be called when a tab is closed. */ private CloseHandler closeHandler; @@ -159,7 +152,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, tabs.remove(c); if (c.equals(selected)) { if (components.isEmpty()) { - selected = null; + setSelected(null); } else { // select the first enabled and visible tab, if any updateSelection(); @@ -286,7 +279,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, tabs.put(c, tab); if (selected == null) { - selected = c; + setSelected(c); fireSelectedTabChange(); } super.addComponent(c); @@ -366,7 +359,6 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @throws PaintException * if the paint operation failed. */ - @Override public void paintContent(PaintTarget target) throws PaintException { if (areTabsHidden()) { @@ -379,18 +371,15 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, target.startTag("tabs"); - Collection<Component> orphaned = new HashSet<Component>(paintedTabs); - for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) { final Component component = i.next(); - orphaned.remove(component); - Tab tab = tabs.get(component); target.startTag("tab"); if (!tab.isEnabled() && tab.isVisible()) { - target.addAttribute("disabled", true); + target.addAttribute( + TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED, true); } if (!tab.isVisible()) { @@ -401,23 +390,29 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, target.addAttribute("closable", true); } + // tab icon, caption and description, but used via + // VCaption.updateCaption(uidl) final Resource icon = tab.getIcon(); if (icon != null) { - target.addAttribute("icon", icon); + target.addAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ICON, + icon); } final String caption = tab.getCaption(); if (caption != null && caption.length() > 0) { - target.addAttribute("caption", caption); + target.addAttribute( + TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION, caption); + } + ErrorMessage tabError = tab.getComponentError(); + if (tabError != null) { + target.addAttribute( + TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE, + tabError.getFormattedHtmlMessage()); } - final String description = tab.getDescription(); if (description != null) { - target.addAttribute("description", description); - } - - final ErrorMessage componentError = tab.getComponentError(); - if (componentError != null) { - componentError.paint(target); + target.addAttribute( + TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION, + description); } final String styleName = tab.getStyleName(); @@ -428,17 +423,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, target.addAttribute("key", keyMapper.key(component)); if (component.equals(selected)) { target.addAttribute("selected", true); - if (!paintedTabs.contains(component)) { - // Ensure the component is painted if it hasn't already been - // painted in this tabsheet - component.requestRepaint(); - paintedTabs.add(component); - } - component.paint(target); - } else if (paintedTabs.contains(component)) { - component.paint(target); - } else { - component.requestRepaintRequests(); + LegacyPaint.paint(component, target); } target.endTag("tab"); } @@ -449,10 +434,6 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, target.addVariable(this, "selected", keyMapper.key(selected)); } - // clean possibly orphaned entries in paintedTabs - for (Component component2 : orphaned) { - paintedTabs.remove(component2); - } } /** @@ -589,7 +570,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, */ public void setSelectedTab(Component c) { if (c != null && components.contains(c) && !c.equals(selected)) { - selected = c; + setSelected(c); updateSelection(); fireSelectedTabChange(); requestRepaint(); @@ -597,6 +578,31 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, } /** + * Sets the selected tab in the TabSheet. Ensures that the selected tab is + * repainted if needed. + * + * @param c + * The new selection or null for no selection + */ + private void setSelected(Component c) { + selected = c; + // Repaint of the selected component is needed as only the selected + // component is communicated to the client. Otherwise this will be a + // "cached" update even though the client knows nothing about the + // connector + if (selected instanceof ComponentContainer) { + ((ComponentContainer) selected).requestRepaintAll(); + } else if (selected instanceof Table) { + // Workaround until there's a generic way of telling a component + // that there is no client side state to rely on. See #8642 + ((Table) selected).refreshRowCache(); + } else if (selected != null) { + selected.requestRepaint(); + } + + } + + /** * Sets the selected tab. The tab is identified by the corresponding * {@link Tab Tab} instance. Does nothing if the tabsheet doesn't contain * the given tab. @@ -653,14 +659,14 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, // The current selection is not valid so we need to change // it if (tab.isEnabled() && tab.isVisible()) { - selected = component; + setSelected(component); break; } else { /* * The current selection is not valid but this tab cannot be * selected either. */ - selected = null; + setSelected(null); } } } @@ -677,15 +683,13 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, } // inherits javadoc - @Override public void changeVariables(Object source, Map<String, Object> variables) { if (variables.containsKey("selected")) { - setSelectedTab((Component) keyMapper.get((String) variables - .get("selected"))); + setSelectedTab(keyMapper.get((String) variables.get("selected"))); } if (variables.containsKey("close")) { - final Component tab = (Component) keyMapper.get((String) variables - .get("close")); + final Component tab = keyMapper + .get((String) variables.get("close")); if (tab != null) { closeHandler.onTabClose(this, tab); } @@ -719,7 +723,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, if (selected == oldComponent) { // keep selection w/o selectedTabChange event - selected = newComponent; + setSelected(newComponent); } Tab newTab = tabs.get(newComponent); @@ -897,16 +901,6 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, fireEvent(new SelectedTabChangeEvent(this)); } - @Override - public void removeListener(RepaintRequestListener listener) { - super.removeListener(listener); - if (listener instanceof CommunicationManager) { - // clean the paintedTabs here instead of detach to avoid subtree - // caching issues when detached-attached without render - paintedTabs.clear(); - } - } - /** * Tab meta-data for a component in a {@link TabSheet}. * @@ -1033,12 +1027,11 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, public void setComponentError(ErrorMessage componentError); /** - * Gets the curent error message shown for the tab. + * Gets the current error message shown for the tab. * - * @see AbstractComponent#setComponentError(ErrorMessage) + * TODO currently not sent to the client * - * @param error - * message or null if none + * @see AbstractComponent#setComponentError(ErrorMessage) */ public ErrorMessage getComponentError(); @@ -1068,9 +1061,8 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * </pre> * * <p> - * This method will trigger a - * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent - * RepaintRequestEvent} on the TabSheet to which the Tab belongs. + * This method will trigger a {@link RepaintRequestEvent} on the + * TabSheet to which the Tab belongs. * </p> * * @param styleName @@ -1303,4 +1295,9 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener); } + + @Override + public boolean isComponentVisible(Component childComponent) { + return childComponent == getSelectedTab(); + } } diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index e605ec4f6b..2fffedd9d6 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -20,18 +21,19 @@ import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; +import com.vaadin.Application; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.util.ContainerOrderedWrapper; import com.vaadin.data.util.IndexedContainer; +import com.vaadin.data.util.converter.Converter; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.DataBoundTransferable; import com.vaadin.event.ItemClickEvent; import com.vaadin.event.ItemClickEvent.ItemClickListener; import com.vaadin.event.ItemClickEvent.ItemClickNotifier; -import com.vaadin.event.ItemClickEvent.ItemClickSource; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DragSource; @@ -40,12 +42,13 @@ import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.acceptcriteria.ClientCriterion; import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion; import com.vaadin.terminal.KeyMapper; +import com.vaadin.terminal.LegacyPaint; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VScrollTable; import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable; /** * <p> @@ -70,11 +73,10 @@ import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; * @VERSION@ * @since 3.0 */ -@SuppressWarnings({ "serial", "deprecation" }) -@ClientWidget(VScrollTable.class) +@SuppressWarnings({ "deprecation" }) public class Table extends AbstractSelect implements Action.Container, - Container.Ordered, Container.Sortable, ItemClickSource, - ItemClickNotifier, DragSource, DropTarget { + Container.Ordered, Container.Sortable, ItemClickNotifier, DragSource, + DropTarget, HasComponents { private static final Logger logger = Logger .getLogger(Table.class.getName()); @@ -113,91 +115,215 @@ public class Table extends AbstractSelect implements Action.Container, protected static final int CELL_FIRSTCOL = 5; + public enum Align { + /** + * Left column alignment. <b>This is the default behaviour. </b> + */ + LEFT("b"), + + /** + * Center column alignment. + */ + CENTER("c"), + + /** + * Right column alignment. + */ + RIGHT("e"); + + private String alignment; + + private Align(String alignment) { + this.alignment = alignment; + } + + @Override + public String toString() { + return alignment; + } + + public Align convertStringToAlign(String string) { + if (string == null) { + return null; + } + if (string.equals("b")) { + return Align.LEFT; + } else if (string.equals("c")) { + return Align.CENTER; + } else if (string.equals("e")) { + return Align.RIGHT; + } else { + return null; + } + } + } + /** - * Left column alignment. <b>This is the default behaviour. </b> + * @deprecated from 7.0, use {@link Align#LEFT} instead */ - public static final String ALIGN_LEFT = "b"; + @Deprecated + public static final Align ALIGN_LEFT = Align.LEFT; /** - * Center column alignment. + * @deprecated from 7.0, use {@link Align#CENTER} instead */ - public static final String ALIGN_CENTER = "c"; + @Deprecated + public static final Align ALIGN_CENTER = Align.CENTER; /** - * Right column alignment. + * @deprecated from 7.0, use {@link Align#RIGHT} instead */ - public static final String ALIGN_RIGHT = "e"; + @Deprecated + public static final Align ALIGN_RIGHT = Align.RIGHT; + + public enum ColumnHeaderMode { + /** + * Column headers are hidden. + */ + HIDDEN, + /** + * Property ID:s are used as column headers. + */ + ID, + /** + * Column headers are explicitly specified with + * {@link #setColumnHeaders(String[])}. + */ + EXPLICIT, + /** + * Column headers are explicitly specified with + * {@link #setColumnHeaders(String[])}. If a header is not specified for + * a given property, its property id is used instead. + * <p> + * <b>This is the default behavior. </b> + */ + EXPLICIT_DEFAULTS_ID + } /** - * Column header mode: Column headers are hidden. + * @deprecated from 7.0, use {@link ColumnHeaderMode#HIDDEN} instead */ - public static final int COLUMN_HEADER_MODE_HIDDEN = -1; + @Deprecated + public static final ColumnHeaderMode COLUMN_HEADER_MODE_HIDDEN = ColumnHeaderMode.HIDDEN; /** - * Column header mode: Property ID:s are used as column headers. + * @deprecated from 7.0, use {@link ColumnHeaderMode#ID} instead */ - public static final int COLUMN_HEADER_MODE_ID = 0; + @Deprecated + public static final ColumnHeaderMode COLUMN_HEADER_MODE_ID = ColumnHeaderMode.ID; /** - * Column header mode: Column headers are explicitly specified with - * {@link #setColumnHeaders(String[])}. + * @deprecated from 7.0, use {@link ColumnHeaderMode#EXPLICIT} instead */ - public static final int COLUMN_HEADER_MODE_EXPLICIT = 1; + @Deprecated + public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT = ColumnHeaderMode.EXPLICIT; /** - * Column header mode: Column headers are explicitly specified with - * {@link #setColumnHeaders(String[])}. If a header is not specified for a - * given property, its property id is used instead. - * <p> - * <b>This is the default behavior. </b> + * @deprecated from 7.0, use {@link ColumnHeaderMode#EXPLICIT_DEFAULTS_ID} + * instead */ - public static final int COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = 2; + @Deprecated + public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = ColumnHeaderMode.EXPLICIT_DEFAULTS_ID; + + public enum RowHeaderMode { + /** + * Row caption mode: The row headers are hidden. <b>This is the default + * mode. </b> + */ + HIDDEN(null), + /** + * Row caption mode: Items Id-objects toString is used as row caption. + */ + ID(ItemCaptionMode.ID), + /** + * Row caption mode: Item-objects toString is used as row caption. + */ + ITEM(ItemCaptionMode.ITEM), + /** + * Row caption mode: Index of the item is used as item caption. The + * index mode can only be used with the containers implementing the + * {@link com.vaadin.data.Container.Indexed} interface. + */ + INDEX(ItemCaptionMode.INDEX), + /** + * Row caption mode: Item captions are explicitly specified, but if the + * caption is missing, the item id objects <code>toString()</code> is + * used instead. + */ + EXPLICIT_DEFAULTS_ID(ItemCaptionMode.EXPLICIT_DEFAULTS_ID), + /** + * Row caption mode: Item captions are explicitly specified. + */ + EXPLICIT(ItemCaptionMode.EXPLICIT), + /** + * Row caption mode: Only icons are shown, the captions are hidden. + */ + ICON_ONLY(ItemCaptionMode.ICON_ONLY), + /** + * Row caption mode: Item captions are read from property specified with + * {@link #setItemCaptionPropertyId(Object)}. + */ + PROPERTY(ItemCaptionMode.PROPERTY); + + ItemCaptionMode mode; + + private RowHeaderMode(ItemCaptionMode mode) { + this.mode = mode; + } + + public ItemCaptionMode getItemCaptionMode() { + return mode; + } + } /** - * Row caption mode: The row headers are hidden. <b>This is the default - * mode. </b> + * @deprecated from 7.0, use {@link RowHeaderMode#HIDDEN} instead */ - public static final int ROW_HEADER_MODE_HIDDEN = -1; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_HIDDEN = RowHeaderMode.HIDDEN; /** - * Row caption mode: Items Id-objects toString is used as row caption. + * @deprecated from 7.0, use {@link RowHeaderMode#ID} instead */ - public static final int ROW_HEADER_MODE_ID = AbstractSelect.ITEM_CAPTION_MODE_ID; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_ID = RowHeaderMode.ID; /** - * Row caption mode: Item-objects toString is used as row caption. + * @deprecated from 7.0, use {@link RowHeaderMode#ITEM} instead */ - public static final int ROW_HEADER_MODE_ITEM = AbstractSelect.ITEM_CAPTION_MODE_ITEM; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_ITEM = RowHeaderMode.ITEM; /** - * Row caption mode: Index of the item is used as item caption. The index - * mode can only be used with the containers implementing Container.Indexed - * interface. + * @deprecated from 7.0, use {@link RowHeaderMode#INDEX} instead */ - public static final int ROW_HEADER_MODE_INDEX = AbstractSelect.ITEM_CAPTION_MODE_INDEX; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_INDEX = RowHeaderMode.INDEX; /** - * Row caption mode: Item captions are explicitly specified. + * @deprecated from 7.0, use {@link RowHeaderMode#EXPLICIT_DEFAULTS_ID} + * instead */ - public static final int ROW_HEADER_MODE_EXPLICIT = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = RowHeaderMode.EXPLICIT_DEFAULTS_ID; /** - * Row caption mode: Item captions are read from property specified with - * {@link #setItemCaptionPropertyId(Object)}. + * @deprecated from 7.0, use {@link RowHeaderMode#EXPLICIT} instead */ - public static final int ROW_HEADER_MODE_PROPERTY = AbstractSelect.ITEM_CAPTION_MODE_PROPERTY; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT = RowHeaderMode.EXPLICIT; /** - * Row caption mode: Only icons are shown, the captions are hidden. + * @deprecated from 7.0, use {@link RowHeaderMode#ICON_ONLY} instead */ - public static final int ROW_HEADER_MODE_ICON_ONLY = AbstractSelect.ITEM_CAPTION_MODE_ICON_ONLY; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_ICON_ONLY = RowHeaderMode.ICON_ONLY; /** - * Row caption mode: Item captions are explicitly specified, but if the - * caption is missing, the item id objects <code>toString()</code> is used - * instead. + * @deprecated from 7.0, use {@link RowHeaderMode#PROPERTY} instead */ - public static final int ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID; + @Deprecated + public static final RowHeaderMode ROW_HEADER_MODE_PROPERTY = RowHeaderMode.PROPERTY; /** * The default rate that table caches rows for smooth scrolling. @@ -222,7 +348,7 @@ public class Table extends AbstractSelect implements Action.Container, /** * Keymapper for column ids. */ - private final KeyMapper columnIdMap = new KeyMapper(); + private final KeyMapper<Object> columnIdMap = new KeyMapper<Object>(); /** * Holds visible column propertyIds - in order. @@ -252,7 +378,7 @@ public class Table extends AbstractSelect implements Action.Container, /** * Holds alignments for visible columns (by propertyId). */ - private HashMap<Object, String> columnAlignments = new HashMap<Object, String>(); + private HashMap<Object, Align> columnAlignments = new HashMap<Object, Align>(); /** * Holds column widths in pixels (Integer) or expand ratios (Float) for @@ -288,17 +414,17 @@ public class Table extends AbstractSelect implements Action.Container, /** * Holds value of property columnHeaderMode. */ - private int columnHeaderMode = COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID; + private ColumnHeaderMode columnHeaderMode = ColumnHeaderMode.EXPLICIT_DEFAULTS_ID; /** - * Should the Table footer be visible? + * Holds value of property rowHeaderMode. */ - private boolean columnFootersVisible = false; + private RowHeaderMode rowHeaderMode = RowHeaderMode.EXPLICIT_DEFAULTS_ID; /** - * True iff the row captions are hidden. + * Should the Table footer be visible? */ - private boolean rowCaptionsAreHidden = true; + private boolean columnFootersVisible = false; /** * Page contents buffer used in buffered mode. @@ -309,7 +435,7 @@ public class Table extends AbstractSelect implements Action.Container, * Set of properties listened - the list is kept to release the listeners * later. */ - private HashSet<Property> listenedProperties = null; + private HashSet<Property<?>> listenedProperties = null; /** * Set of visible components - the is used for needsRepaint calculation. @@ -324,7 +450,7 @@ public class Table extends AbstractSelect implements Action.Container, /** * Action mapper. */ - private KeyMapper actionMapper = null; + private KeyMapper<Action> actionMapper = null; /** * Table cell editor factory. @@ -404,10 +530,12 @@ public class Table extends AbstractSelect implements Action.Container, private RowGenerator rowGenerator = null; - private final Map<Field, Property> associatedProperties = new HashMap<Field, Property>(); + private final Map<Field<?>, Property<?>> associatedProperties = new HashMap<Field<?>, Property<?>>(); private boolean painted = false; + private HashMap<Object, Converter<String, Object>> propertyValueConverters = new HashMap<Object, Converter<String, Object>>(); + /* Table constructors */ /** @@ -508,7 +636,7 @@ public class Table extends AbstractSelect implements Action.Container, final Object col = i.next(); if (!newVC.contains(col)) { setColumnHeader(col, null); - setColumnAlignment(col, null); + setColumnAlignment(col, (Align) null); setColumnIcon(col, null); } } @@ -652,21 +780,21 @@ public class Table extends AbstractSelect implements Action.Container, * {@link #getVisibleColumns()}. The possible values for the alignments * include: * <ul> - * <li>{@link #ALIGN_LEFT}: Left alignment</li> - * <li>{@link #ALIGN_CENTER}: Centered</li> - * <li>{@link #ALIGN_RIGHT}: Right alignment</li> + * <li>{@link Align#LEFT}: Left alignment</li> + * <li>{@link Align#CENTER}: Centered</li> + * <li>{@link Align#RIGHT}: Right alignment</li> * </ul> - * The alignments default to {@link #ALIGN_LEFT}: any null values are + * The alignments default to {@link Align#LEFT}: any null values are * rendered as align lefts. * </p> * * @return the Column alignments array. */ - public String[] getColumnAlignments() { + public Align[] getColumnAlignments() { if (columnAlignments == null) { return null; } - final String[] alignments = new String[visibleColumns.size()]; + final Align[] alignments = new Align[visibleColumns.size()]; int i = 0; for (final Iterator<Object> it = visibleColumns.iterator(); it .hasNext(); i++) { @@ -680,39 +808,29 @@ public class Table extends AbstractSelect implements Action.Container, * Sets the column alignments. * * <p> - * The items in the array must match the properties identified by - * {@link #getVisibleColumns()}. The possible values for the alignments - * include: + * The amount of items in the array must match the amount of properties + * identified by {@link #getVisibleColumns()}. The possible values for the + * alignments include: * <ul> - * <li>{@link #ALIGN_LEFT}: Left alignment</li> - * <li>{@link #ALIGN_CENTER}: Centered</li> - * <li>{@link #ALIGN_RIGHT}: Right alignment</li> + * <li>{@link Align#LEFT}: Left alignment</li> + * <li>{@link Align#CENTER}: Centered</li> + * <li>{@link Align#RIGHT}: Right alignment</li> * </ul> - * The alignments default to {@link #ALIGN_LEFT} + * The alignments default to {@link Align#LEFT} * </p> * * @param columnAlignments * the Column alignments array. */ - public void setColumnAlignments(String[] columnAlignments) { + public void setColumnAlignments(Align... columnAlignments) { if (columnAlignments.length != visibleColumns.size()) { throw new IllegalArgumentException( "The length of the alignments array must match the number of visible columns"); } - // Checks all alignments - for (int i = 0; i < columnAlignments.length; i++) { - final String a = columnAlignments[i]; - if (a != null && !a.equals(ALIGN_LEFT) && !a.equals(ALIGN_CENTER) - && !a.equals(ALIGN_RIGHT)) { - throw new IllegalArgumentException("Column " + i - + " aligment '" + a + "' is invalid"); - } - } - // Resets the alignments - final HashMap<Object, String> newCA = new HashMap<Object, String>(); + final HashMap<Object, Align> newCA = new HashMap<Object, Align>(); int i = 0; for (final Iterator<Object> it = visibleColumns.iterator(); it .hasNext() && i < columnAlignments.length; i++) { @@ -1032,13 +1150,13 @@ public class Table extends AbstractSelect implements Action.Container, * @return the header for the specified column if it has one. */ public String getColumnHeader(Object propertyId) { - if (getColumnHeaderMode() == COLUMN_HEADER_MODE_HIDDEN) { + if (getColumnHeaderMode() == ColumnHeaderMode.HIDDEN) { return null; } String header = columnHeaders.get(propertyId); - if ((header == null && getColumnHeaderMode() == COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID) - || getColumnHeaderMode() == COLUMN_HEADER_MODE_ID) { + if ((header == null && getColumnHeaderMode() == ColumnHeaderMode.EXPLICIT_DEFAULTS_ID) + || getColumnHeaderMode() == ColumnHeaderMode.ID) { header = propertyId.toString(); } @@ -1071,9 +1189,9 @@ public class Table extends AbstractSelect implements Action.Container, * the propertyID identifying the column. * @return the specified column's alignment if it as one; null otherwise. */ - public String getColumnAlignment(Object propertyId) { - final String a = columnAlignments.get(propertyId); - return a == null ? ALIGN_LEFT : a; + public Align getColumnAlignment(Object propertyId) { + final Align a = columnAlignments.get(propertyId); + return a == null ? Align.LEFT : a; } /** @@ -1081,8 +1199,8 @@ public class Table extends AbstractSelect implements Action.Container, * * <p> * Throws IllegalArgumentException if the alignment is not one of the - * following: {@link #ALIGN_LEFT}, {@link #ALIGN_CENTER} or - * {@link #ALIGN_RIGHT} + * following: {@link Align#LEFT}, {@link Align#CENTER} or + * {@link Align#RIGHT} * </p> * * @param propertyId @@ -1090,17 +1208,8 @@ public class Table extends AbstractSelect implements Action.Container, * @param alignment * the desired alignment. */ - public void setColumnAlignment(Object propertyId, String alignment) { - - // Checks for valid alignments - if (alignment != null && !alignment.equals(ALIGN_LEFT) - && !alignment.equals(ALIGN_CENTER) - && !alignment.equals(ALIGN_RIGHT)) { - throw new IllegalArgumentException("Column alignment '" + alignment - + "' is not supported."); - } - - if (alignment == null || alignment.equals(ALIGN_LEFT)) { + public void setColumnAlignment(Object propertyId, Align alignment) { + if (alignment == null || alignment == Align.LEFT) { columnAlignments.remove(propertyId); } else { columnAlignments.put(propertyId, alignment); @@ -1400,7 +1509,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @return the Value of property columnHeaderMode. */ - public int getColumnHeaderMode() { + public ColumnHeaderMode getColumnHeaderMode() { return columnHeaderMode; } @@ -1410,10 +1519,12 @@ public class Table extends AbstractSelect implements Action.Container, * @param columnHeaderMode * the New value of property columnHeaderMode. */ - public void setColumnHeaderMode(int columnHeaderMode) { - if (columnHeaderMode != this.columnHeaderMode - && columnHeaderMode >= COLUMN_HEADER_MODE_HIDDEN - && columnHeaderMode <= COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID) { + public void setColumnHeaderMode(ColumnHeaderMode columnHeaderMode) { + if (columnHeaderMode == null) { + throw new IllegalArgumentException( + "Column header mode can not be null"); + } + if (columnHeaderMode != this.columnHeaderMode) { this.columnHeaderMode = columnHeaderMode; requestRepaint(); } @@ -1725,13 +1836,13 @@ public class Table extends AbstractSelect implements Action.Container, final Object[] colids = getVisibleColumns(); final int cols = colids.length; - HashSet<Property> oldListenedProperties = listenedProperties; + HashSet<Property<?>> oldListenedProperties = listenedProperties; HashSet<Component> oldVisibleComponents = visibleComponents; if (replaceListeners) { // initialize the listener collections, this should only be done if // the entire cache is refreshed (through refreshRenderedCells) - listenedProperties = new HashSet<Property>(); + listenedProperties = new HashSet<Property<?>>(); visibleComponents = new HashSet<Component>(); } @@ -1753,7 +1864,7 @@ public class Table extends AbstractSelect implements Action.Container, } } - final int headmode = getRowHeaderMode(); + final RowHeaderMode headmode = getRowHeaderMode(); final boolean[] iscomponent = new boolean[cols]; for (int i = 0; i < cols; i++) { iscomponent[i] = columnGenerators.containsKey(colids[i]) @@ -1774,7 +1885,7 @@ public class Table extends AbstractSelect implements Action.Container, cells[CELL_KEY][i] = itemIdMapper.key(id); if (headmode != ROW_HEADER_MODE_HIDDEN) { switch (headmode) { - case ROW_HEADER_MODE_INDEX: + case INDEX: cells[CELL_HEADER][i] = String.valueOf(i + firstIndex + 1); break; default: @@ -1791,7 +1902,7 @@ public class Table extends AbstractSelect implements Action.Container, if (isColumnCollapsed(colids[j])) { continue; } - Property p = null; + Property<?> p = null; Object value = ""; boolean isGeneratedRow = generatedRow != null; boolean isGeneratedColumn = columnGenerators @@ -1911,8 +2022,8 @@ public class Table extends AbstractSelect implements Action.Container, visibleComponents.add(component); } - private void listenProperty(Property p, - HashSet<Property> oldListenedProperties) { + private void listenProperty(Property<?> p, + HashSet<Property<?>> oldListenedProperties) { if (p instanceof Property.ValueChangeNotifier) { if (oldListenedProperties == null || !oldListenedProperties.contains(p)) { @@ -1952,7 +2063,7 @@ public class Table extends AbstractSelect implements Action.Container, visibleComponents.remove(cellVal); unregisterComponent((Component) cellVal); } else { - Property p = getContainerProperty( + Property<?> p = getContainerProperty( pageBuffer[CELL_ITEMID][i + ix], colids[c]); if (p instanceof ValueChangeNotifier && listenedProperties.contains(p)) { @@ -1977,7 +2088,7 @@ public class Table extends AbstractSelect implements Action.Container, * set of components that where attached in last render */ private void unregisterPropertiesAndComponents( - HashSet<Property> oldListenedProperties, + HashSet<Property<?>> oldListenedProperties, HashSet<Component> oldVisibleComponents) { if (oldVisibleComponents != null) { for (final Iterator<Component> i = oldVisibleComponents.iterator(); i @@ -1990,8 +2101,8 @@ public class Table extends AbstractSelect implements Action.Container, } if (oldListenedProperties != null) { - for (final Iterator<Property> i = oldListenedProperties.iterator(); i - .hasNext();) { + for (final Iterator<Property<?>> i = oldListenedProperties + .iterator(); i.hasNext();) { Property.ValueChangeNotifier o = (ValueChangeNotifier) i.next(); if (!listenedProperties.contains(o)) { o.removeListener(this); @@ -2024,8 +2135,8 @@ public class Table extends AbstractSelect implements Action.Container, * fields in memory. */ if (component instanceof Field) { - Field field = (Field) component; - Property associatedProperty = associatedProperties + Field<?> field = (Field<?>) component; + Property<?> associatedProperty = associatedProperties .remove(component); if (associatedProperty != null && field.getPropertyDataSource() == associatedProperty) { @@ -2073,17 +2184,17 @@ public class Table extends AbstractSelect implements Action.Container, * @param mode * the One of the modes listed above. */ - public void setRowHeaderMode(int mode) { - if (ROW_HEADER_MODE_HIDDEN == mode) { - rowCaptionsAreHidden = true; - } else { - rowCaptionsAreHidden = false; - setItemCaptionMode(mode); + public void setRowHeaderMode(RowHeaderMode mode) { + if (mode != null) { + rowHeaderMode = mode; + if (mode != RowHeaderMode.HIDDEN) { + setItemCaptionMode(mode.getItemCaptionMode()); + } + // Assures the visual refresh. No need to reset the page buffer + // before + // as the content has not changed, only the alignments. + refreshRenderedCells(); } - - // Assures the visual refresh. No need to reset the page buffer before - // as the content has not changed, only the alignments. - refreshRenderedCells(); } /** @@ -2092,9 +2203,8 @@ public class Table extends AbstractSelect implements Action.Container, * @return the Row header mode. * @see #setRowHeaderMode(int) */ - public int getRowHeaderMode() { - return rowCaptionsAreHidden ? ROW_HEADER_MODE_HIDDEN - : getItemCaptionMode(); + public RowHeaderMode getRowHeaderMode() { + return rowHeaderMode; } /** @@ -2491,7 +2601,7 @@ public class Table extends AbstractSelect implements Action.Container, (String) variables.get("action"), ","); if (st.countTokens() == 2) { final Object itemId = itemIdMapper.get(st.nextToken()); - final Action action = (Action) actionMapper.get(st.nextToken()); + final Action action = actionMapper.get(st.nextToken()); if (action != null && (itemId == null || containsId(itemId)) && actionHandlers != null) { @@ -2931,7 +3041,7 @@ public class Table extends AbstractSelect implements Action.Container, } private boolean areColumnHeadersEnabled() { - return getColumnHeaderMode() != COLUMN_HEADER_MODE_HIDDEN; + return getColumnHeaderMode() != ColumnHeaderMode.HIDDEN; } private void paintVisibleColumns(PaintTarget target) throws PaintException { @@ -2962,8 +3072,9 @@ public class Table extends AbstractSelect implements Action.Container, target.addAttribute("sortable", true); } } - if (!ALIGN_LEFT.equals(getColumnAlignment(colId))) { - target.addAttribute("align", getColumnAlignment(colId)); + if (!Align.LEFT.equals(getColumnAlignment(colId))) { + target.addAttribute("align", getColumnAlignment(colId) + .toString()); } paintColumnWidth(target, colId); target.endTag("column"); @@ -3253,7 +3364,7 @@ public class Table extends AbstractSelect implements Action.Container, target.addText(""); paintCellTooltips(target, itemId, columnId); } else { - c.paint(target); + LegacyPaint.paint(c, target); } } else { target.addText((String) cells[CELL_FIRSTCOL + currentColumn][indexInRowbuffer]); @@ -3409,8 +3520,8 @@ public class Table extends AbstractSelect implements Action.Container, protected Object getPropertyValue(Object rowId, Object colId, Property property) { if (isEditable() && fieldFactory != null) { - final Field f = fieldFactory.createField(getContainerDataSource(), - rowId, colId, this); + final Field<?> f = fieldFactory.createField( + getContainerDataSource(), rowId, colId, this); if (f != null) { // Remember that we have made this association so we can remove // it when the component is removed @@ -3464,11 +3575,27 @@ public class Table extends AbstractSelect implements Action.Container, * @since 3.1 */ protected String formatPropertyValue(Object rowId, Object colId, - Property property) { + Property<?> property) { if (property == null) { return ""; } - return property.toString(); + Converter<String, Object> converter = null; + + if (hasConverter(colId)) { + converter = getConverter(colId); + } else { + Application app = Application.getCurrentApplication(); + if (app != null) { + converter = (Converter<String, Object>) app + .getConverterFactory().createConverter(String.class, + property.getType()); + } + } + Object value = property.getValue(); + if (converter != null) { + return converter.convertToPresentation(value, getLocale()); + } + return (null != value) ? value.toString() : ""; } /* Action container */ @@ -3484,7 +3611,7 @@ public class Table extends AbstractSelect implements Action.Container, if (actionHandlers == null) { actionHandlers = new LinkedList<Handler>(); - actionMapper = new KeyMapper(); + actionMapper = new KeyMapper<Action>(); } if (!actionHandlers.contains(actionHandler)) { @@ -3711,7 +3838,7 @@ public class Table extends AbstractSelect implements Action.Container, */ public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue, String columnHeader, Resource columnIcon, - String columnAlignment) throws UnsupportedOperationException { + Align columnAlignment) throws UnsupportedOperationException { if (!this.addContainerProperty(propertyId, type, defaultValue)) { return false; } @@ -4029,44 +4156,6 @@ public class Table extends AbstractSelect implements Action.Container, } /** - * Gets the FieldFactory that is used to create editor for table cells. - * - * The FieldFactory is only used if the Table is editable. - * - * @return FieldFactory used to create the Field instances. - * @see #isEditable - * @deprecated use {@link #getTableFieldFactory()} instead - */ - @Deprecated - public FieldFactory getFieldFactory() { - if (fieldFactory instanceof FieldFactory) { - return (FieldFactory) fieldFactory; - - } - return null; - } - - /** - * Sets the FieldFactory that is used to create editor for table cells. - * - * The FieldFactory is only used if the Table is editable. By default the - * BaseFieldFactory is used. - * - * @param fieldFactory - * the field factory to set. - * @see #isEditable - * @see BaseFieldFactory - * @deprecated use {@link #setTableFieldFactory(TableFieldFactory)} instead - */ - @Deprecated - public void setFieldFactory(FieldFactory fieldFactory) { - this.fieldFactory = fieldFactory; - - // Assure visual refresh - refreshRowCache(); - } - - /** * Is table editable. * * If table is editable a editor of type Field is created for each table @@ -4377,27 +4466,8 @@ public class Table extends AbstractSelect implements Action.Container, } } - // Virtually identical to AbstractCompoenentContainer.setEnabled(); public void requestRepaintAll() { - requestRepaint(); - if (visibleComponents != null) { - for (Iterator<Component> childIterator = visibleComponents - .iterator(); childIterator.hasNext();) { - Component c = childIterator.next(); - if (c instanceof Form) { - // Form has children in layout, but is not - // ComponentContainer - c.requestRepaint(); - ((Form) c).getLayout().requestRepaintAll(); - } else if (c instanceof Table) { - ((Table) c).requestRepaintAll(); - } else if (c instanceof ComponentContainer) { - ((ComponentContainer) c).requestRepaintAll(); - } else { - c.requestRepaint(); - } - } - } + AbstractComponentContainer.requestRepaintAll(this); } /** @@ -5147,13 +5217,86 @@ public class Table extends AbstractSelect implements Action.Container, return rowGenerator; } + /** + * Sets a converter for a property id. + * <p> + * The converter is used to format the the data for the given property id + * before displaying it in the table. + * </p> + * + * @param propertyId + * The propertyId to format using the converter + * @param converter + * The converter to use for the property id + */ + public void setConverter(Object propertyId, Converter<String, ?> converter) { + if (!getContainerPropertyIds().contains(propertyId)) { + throw new IllegalArgumentException("PropertyId " + propertyId + + " must be in the container"); + } + // FIXME: This check should be here but primitive types like Boolean + // formatter for boolean property must be handled + + // if (!converter.getSourceType().isAssignableFrom(getType(propertyId))) + // { + // throw new IllegalArgumentException("Property type (" + // + getType(propertyId) + // + ") must match converter source type (" + // + converter.getSourceType() + ")"); + // } + propertyValueConverters.put(propertyId, + (Converter<String, Object>) converter); + refreshRowCache(); + } + + /** + * Checks if there is a converter set explicitly for the given property id. + * + * @param propertyId + * The propertyId to check + * @return true if a converter has been set for the property id, false + * otherwise + */ + protected boolean hasConverter(Object propertyId) { + return propertyValueConverters.containsKey(propertyId); + } + + /** + * Returns the converter used to format the given propertyId. + * + * @param propertyId + * The propertyId to check + * @return The converter used to format the propertyId or null if no + * converter has been set + */ + public Converter<String, Object> getConverter(Object propertyId) { + return propertyValueConverters.get(propertyId); + } + @Override public void setVisible(boolean visible) { - if (!isVisible() && visible) { + if (visible) { // We need to ensure that the rows are sent to the client when the // Table is made visible if it has been rendered as invisible. setRowCacheInvalidated(true); } super.setVisible(visible); } + + public Iterator<Component> iterator() { + return getComponentIterator(); + } + + public Iterator<Component> getComponentIterator() { + if (visibleComponents == null) { + Collection<Component> empty = Collections.emptyList(); + return empty.iterator(); + } + + return visibleComponents.iterator(); + } + + public boolean isComponentVisible(Component childComponent) { + return true; + } } diff --git a/src/com/vaadin/ui/TableFieldFactory.java b/src/com/vaadin/ui/TableFieldFactory.java index 9b0495d589..6c9a641aa8 100644 --- a/src/com/vaadin/ui/TableFieldFactory.java +++ b/src/com/vaadin/ui/TableFieldFactory.java @@ -39,7 +39,7 @@ public interface TableFieldFactory extends Serializable { * @return A field suitable for editing the specified data or null if the * property should not be editable. */ - Field createField(Container container, Object itemId, Object propertyId, + Field<?> createField(Container container, Object itemId, Object propertyId, Component uiContext); } diff --git a/src/com/vaadin/ui/TextArea.java b/src/com/vaadin/ui/TextArea.java index e1e5aeabd4..adb980818e 100644 --- a/src/com/vaadin/ui/TextArea.java +++ b/src/com/vaadin/ui/TextArea.java @@ -7,12 +7,10 @@ package com.vaadin.ui; import com.vaadin.data.Property; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VTextArea; /** * A text field that supports multi line editing. */ -@ClientWidget(VTextArea.class) public class TextArea extends AbstractTextField { private static final int DEFAULT_ROWS = 5; diff --git a/src/com/vaadin/ui/TextField.java b/src/com/vaadin/ui/TextField.java index 0d719efd12..567e9c1c10 100644 --- a/src/com/vaadin/ui/TextField.java +++ b/src/com/vaadin/ui/TextField.java @@ -5,10 +5,6 @@ package com.vaadin.ui; import com.vaadin.data.Property; -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VTextField; -import com.vaadin.ui.ClientWidget.LoadStyle; /** * <p> @@ -31,30 +27,9 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VTextField.class, loadStyle = LoadStyle.EAGER) public class TextField extends AbstractTextField { /** - * Tells if input is used to enter sensitive information that is not echoed - * to display. Typically passwords. - */ - @Deprecated - private boolean secret = false; - - /** - * Number of visible rows in a multiline TextField. Value 0 implies a - * single-line text-editor. - */ - @Deprecated - private int rows = 0; - - /** - * Tells if word-wrapping should be used in multiline mode. - */ - @Deprecated - private boolean wordwrap = true; - - /** * Constructs an empty <code>TextField</code> with no caption. */ public TextField() { @@ -106,7 +81,7 @@ public class TextField extends AbstractTextField { * * @param caption * the caption <code>String</code> for the editor. - * @param text + * @param value * the initial text content of the editor. */ public TextField(String caption, String value) { @@ -114,180 +89,4 @@ public class TextField extends AbstractTextField { setCaption(caption); } - /** - * Gets the secret property. If a field is used to enter secret information - * the information is not echoed to display. - * - * @return <code>true</code> if the field is used to enter secret - * information, <code>false</code> otherwise. - * - * @deprecated Starting from 6.5 use {@link PasswordField} instead for - * secret text input. - */ - @Deprecated - public boolean isSecret() { - return secret; - } - - /** - * Sets the secret property on and off. If a field is used to enter secret - * information the information is not echoed to display. - * - * @param secret - * the value specifying if the field is used to enter secret - * information. - * @deprecated Starting from 6.5 use {@link PasswordField} instead for - * secret text input. - */ - @Deprecated - public void setSecret(boolean secret) { - if (this.secret != secret) { - this.secret = secret; - requestRepaint(); - } - } - - @Override - public void paintContent(PaintTarget target) throws PaintException { - if (isSecret()) { - target.addAttribute("secret", true); - } - - final int rows = getRows(); - if (rows != 0) { - target.addAttribute("rows", rows); - target.addAttribute("multiline", true); - - if (!isWordwrap()) { - // Wordwrap is only painted if turned off to minimize - // communications - target.addAttribute("wordwrap", false); - } - } - - super.paintContent(target); - - } - - /** - * Gets the number of rows in the editor. If the number of rows is set to 0, - * the actual number of displayed rows is determined implicitly by the - * adapter. - * - * @return number of explicitly set rows. - * @deprecated Starting from 6.5 use {@link TextArea} for a multi-line text - * input. - * - */ - @Deprecated - public int getRows() { - return rows; - } - - /** - * Sets the number of rows in the editor. - * - * @param rows - * the number of rows for this editor. - * - * @deprecated Starting from 6.5 use {@link TextArea} for a multi-line text - * input. - */ - @Deprecated - public void setRows(int rows) { - if (rows < 0) { - rows = 0; - } - if (this.rows != rows) { - this.rows = rows; - requestRepaint(); - } - } - - /** - * Tests if the editor is in word-wrap mode. - * - * @return <code>true</code> if the component is in the word-wrap mode, - * <code>false</code> if not. - * @deprecated Starting from 6.5 use {@link TextArea} for a multi-line text - * input. - */ - @Deprecated - public boolean isWordwrap() { - return wordwrap; - } - - /** - * Sets the editor's word-wrap mode on or off. - * - * @param wordwrap - * the boolean value specifying if the editor should be in - * word-wrap mode after the call or not. - * - * @deprecated Starting from 6.5 use {@link TextArea} for a multi-line text - * input. - */ - @Deprecated - public void setWordwrap(boolean wordwrap) { - if (this.wordwrap != wordwrap) { - this.wordwrap = wordwrap; - requestRepaint(); - } - } - - /** - * Sets the height of the {@link TextField} instance. - * - * <p> - * Setting height for {@link TextField} also has a side-effect that puts - * {@link TextField} into multiline mode (aka "textarea"). Multiline mode - * can also be achieved by calling {@link #setRows(int)}. The height value - * overrides the number of rows set by {@link #setRows(int)}. - * <p> - * If you want to set height of single line {@link TextField}, call - * {@link #setRows(int)} with value 0 after setting the height. Setting rows - * to 0 resets the side-effect. - * <p> - * Starting from 6.5 you should use {@link TextArea} instead of - * {@link TextField} for multiline text input. - * - * - * @see com.vaadin.ui.AbstractComponent#setHeight(float, int) - */ - @Override - public void setHeight(float height, int unit) { - super.setHeight(height, unit); - if (height > 1 && getClass() == TextField.class) { - /* - * In html based terminals we most commonly want to make component - * to be textarea if height is defined. Setting row field above 0 - * will render component as textarea. - */ - - setRows(2); - } - } - - /** - * Sets the height of the {@link TextField} instance. - * - * <p> - * Setting height for {@link TextField} also has a side-effect that puts - * {@link TextField} into multiline mode (aka "textarea"). Multiline mode - * can also be achieved by calling {@link #setRows(int)}. The height value - * overrides the number of rows set by {@link #setRows(int)}. - * <p> - * If you want to set height of single line {@link TextField}, call - * {@link #setRows(int)} with value 0 after setting the height. Setting rows - * to 0 resets the side-effect. - * - * @see com.vaadin.ui.AbstractComponent#setHeight(java.lang.String) - */ - @Override - public void setHeight(String height) { - // will call setHeight(float, int) the actually does the magic. Method - // is overridden just to document side-effects. - super.setHeight(height); - } - } diff --git a/src/com/vaadin/ui/Tree.java b/src/com/vaadin/ui/Tree.java index 554afda97c..db738fee58 100644 --- a/src/com/vaadin/ui/Tree.java +++ b/src/com/vaadin/ui/Tree.java @@ -28,7 +28,6 @@ import com.vaadin.event.DataBoundTransferable; import com.vaadin.event.ItemClickEvent; import com.vaadin.event.ItemClickEvent.ItemClickListener; import com.vaadin.event.ItemClickEvent.ItemClickNotifier; -import com.vaadin.event.ItemClickEvent.ItemClickSource; import com.vaadin.event.Transferable; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DragSource; @@ -44,10 +43,11 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VTree; import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; import com.vaadin.terminal.gwt.client.ui.dd.VTargetInSubtree; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; +import com.vaadin.terminal.gwt.client.ui.tree.TreeConnector; +import com.vaadin.terminal.gwt.client.ui.tree.VTree; import com.vaadin.tools.ReflectTools; /** @@ -60,10 +60,8 @@ import com.vaadin.tools.ReflectTools; * @since 3.0 */ @SuppressWarnings({ "serial", "deprecation" }) -@ClientWidget(VTree.class) public class Tree extends AbstractSelect implements Container.Hierarchical, - Action.Container, ItemClickSource, ItemClickNotifier, DragSource, - DropTarget { + Action.Container, ItemClickNotifier, DragSource, DropTarget { /* Private members */ @@ -80,7 +78,7 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, /** * Action mapper. */ - private KeyMapper actionMapper = null; + private KeyMapper<Action> actionMapper = null; /** * Is the tree selectable on the client side. @@ -447,7 +445,7 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, (String) variables.get("action"), ","); if (st.countTokens() == 2) { final Object itemId = itemIdMapper.get(st.nextToken()); - final Action action = (Action) actionMapper.get(st.nextToken()); + final Action action = actionMapper.get(st.nextToken()); if (action != null && (itemId == null || containsId(itemId)) && actionHandlers != null) { for (Handler ah : actionHandlers) { @@ -609,7 +607,8 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, if (itemStyleGenerator != null) { String stylename = itemStyleGenerator.getStyle(itemId); if (stylename != null) { - target.addAttribute("style", stylename); + target.addAttribute(TreeConnector.ATTRIBUTE_NODE_STYLE, + stylename); } } @@ -622,10 +621,12 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, } // Adds the attributes - target.addAttribute("caption", getItemCaption(itemId)); + target.addAttribute(TreeConnector.ATTRIBUTE_NODE_CAPTION, + getItemCaption(itemId)); final Resource icon = getItemIcon(itemId); if (icon != null) { - target.addAttribute("icon", getItemIcon(itemId)); + target.addAttribute(TreeConnector.ATTRIBUTE_NODE_ICON, + getItemIcon(itemId)); } final String key = itemIdMapper.key(itemId); target.addAttribute("key", key); @@ -682,10 +683,12 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, final Action a = i.next(); target.startTag("action"); if (a.getCaption() != null) { - target.addAttribute("caption", a.getCaption()); + target.addAttribute(TreeConnector.ATTRIBUTE_ACTION_CAPTION, + a.getCaption()); } if (a.getIcon() != null) { - target.addAttribute("icon", a.getIcon()); + target.addAttribute(TreeConnector.ATTRIBUTE_ACTION_ICON, + a.getIcon()); } target.addAttribute("key", actionMapper.key(a)); target.endTag("action"); @@ -1021,7 +1024,7 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, if (actionHandlers == null) { actionHandlers = new LinkedList<Action.Handler>(); - actionMapper = new KeyMapper(); + actionMapper = new KeyMapper<Action>(); } if (!actionHandlers.contains(actionHandler)) { diff --git a/src/com/vaadin/ui/TreeTable.java b/src/com/vaadin/ui/TreeTable.java index 43bc7a80fe..9607add2c9 100644 --- a/src/com/vaadin/ui/TreeTable.java +++ b/src/com/vaadin/ui/TreeTable.java @@ -13,22 +13,21 @@ import java.util.List; import java.util.Map; import java.util.logging.Logger; -import com.google.gwt.user.client.ui.Tree; +import com.vaadin.data.Collapsible; import com.vaadin.data.Container; import com.vaadin.data.Container.Hierarchical; import com.vaadin.data.Container.ItemSetChangeEvent; import com.vaadin.data.util.ContainerHierarchicalWrapper; import com.vaadin.data.util.HierarchicalContainer; +import com.vaadin.data.util.HierarchicalContainerOrderedWrapper; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VTreeTable; +import com.vaadin.terminal.gwt.client.ui.treetable.TreeTableConnector; import com.vaadin.ui.Tree.CollapseEvent; import com.vaadin.ui.Tree.CollapseListener; import com.vaadin.ui.Tree.ExpandEvent; import com.vaadin.ui.Tree.ExpandListener; -import com.vaadin.ui.treetable.Collapsible; -import com.vaadin.ui.treetable.HierarchicalContainerOrderedWrapper; /** * TreeTable extends the {@link Table} component so that it can also visualize a @@ -48,7 +47,6 @@ import com.vaadin.ui.treetable.HierarchicalContainerOrderedWrapper; * share UI state in the container. */ @SuppressWarnings({ "serial" }) -@ClientWidget(VTreeTable.class) public class TreeTable extends Table implements Hierarchical { private static final Logger logger = Logger.getLogger(TreeTable.class @@ -454,7 +452,8 @@ public class TreeTable extends Table implements Hierarchical { Object object = visibleColumns2[i]; if (hierarchyColumnId.equals(object)) { target.addAttribute( - VTreeTable.ATTRIBUTE_HIERARCHY_COLUMN_INDEX, i); + TreeTableConnector.ATTRIBUTE_HIERARCHY_COLUMN_INDEX, + i); break; } } @@ -552,7 +551,9 @@ public class TreeTable extends Table implements Hierarchical { public void setContainerDataSource(Container newDataSource) { cStrategy = null; - containerSupportsPartialUpdates = (newDataSource instanceof ItemSetChangeNotifier); + // FIXME: This disables partial updates until TreeTable is fixed so it + // does not change component hierarchy during paint + containerSupportsPartialUpdates = (newDataSource instanceof ItemSetChangeNotifier) && false; if (!(newDataSource instanceof Hierarchical)) { newDataSource = new ContainerHierarchicalWrapper(newDataSource); diff --git a/src/com/vaadin/ui/TwinColSelect.java b/src/com/vaadin/ui/TwinColSelect.java index 1c1fe07a5c..5539236f77 100644 --- a/src/com/vaadin/ui/TwinColSelect.java +++ b/src/com/vaadin/ui/TwinColSelect.java @@ -9,14 +9,13 @@ import java.util.Collection; import com.vaadin.data.Container; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VTwinColSelect; +import com.vaadin.terminal.gwt.client.ui.twincolselect.VTwinColSelect; /** * Multiselect component with two lists: left side for available items and right * side for selected items. */ @SuppressWarnings("serial") -@ClientWidget(VTwinColSelect.class) public class TwinColSelect extends AbstractSelect { private int columns = 0; diff --git a/src/com/vaadin/ui/Upload.java b/src/com/vaadin/ui/Upload.java index 9d684291a5..4dff71e45b 100644 --- a/src/com/vaadin/ui/Upload.java +++ b/src/com/vaadin/ui/Upload.java @@ -15,10 +15,9 @@ import java.util.Map; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.StreamVariable.StreamingProgressEvent; -import com.vaadin.terminal.gwt.client.ui.VUpload; +import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.server.NoInputStreamException; import com.vaadin.terminal.gwt.server.NoOutputStreamException; -import com.vaadin.ui.ClientWidget.LoadStyle; /** * Component for uploading files from client to server. @@ -61,8 +60,8 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VUpload.class, loadStyle = LoadStyle.LAZY) -public class Upload extends AbstractComponent implements Component.Focusable { +public class Upload extends AbstractComponent implements Component.Focusable, + Vaadin6Component { /** * Should the field be focused on next repaint? @@ -123,7 +122,6 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, * java.util.Map) */ - @Override public void changeVariables(Object source, Map<String, Object> variables) { if (variables.containsKey("pollForStart")) { int id = (Integer) variables.get("pollForStart"); @@ -143,7 +141,6 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @throws PaintException * if the paint operation failed. */ - @Override public void paintContent(PaintTarget target) throws PaintException { if (notStarted) { target.addAttribute("notStarted", true); diff --git a/src/com/vaadin/ui/UriFragmentUtility.java b/src/com/vaadin/ui/UriFragmentUtility.java deleted file mode 100644 index 5eaffbde6f..0000000000 --- a/src/com/vaadin/ui/UriFragmentUtility.java +++ /dev/null @@ -1,153 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.ui; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.Map; - -import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VUriFragmentUtility; -import com.vaadin.ui.ClientWidget.LoadStyle; - -/** - * Experimental web browser dependent component for URI fragment (part after - * hash mark "#") reading and writing. - * - * Component can be used to workaround common ajax web applications pitfalls: - * bookmarking a program state and back button. - * - */ -@SuppressWarnings("serial") -@ClientWidget(value = VUriFragmentUtility.class, loadStyle = LoadStyle.EAGER) -public class UriFragmentUtility extends AbstractComponent { - - /** - * Listener that listens changes in URI fragment. - */ - public interface FragmentChangedListener extends Serializable { - - public void fragmentChanged(FragmentChangedEvent source); - - } - - /** - * Event fired when uri fragment changes. - */ - public class FragmentChangedEvent extends Component.Event { - - /** - * Creates a new instance of UriFragmentReader change event. - * - * @param source - * the Source of the event. - */ - public FragmentChangedEvent(Component source) { - super(source); - } - - /** - * Gets the UriFragmentReader where the event occurred. - * - * @return the Source of the event. - */ - public UriFragmentUtility getUriFragmentUtility() { - return (UriFragmentUtility) getSource(); - } - } - - 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"); - } - } - - public void addListener(FragmentChangedListener listener) { - addListener(FragmentChangedEvent.class, listener, - FRAGMENT_CHANGED_METHOD); - } - - public void removeListener(FragmentChangedListener listener) { - removeListener(FragmentChangedEvent.class, listener, - FRAGMENT_CHANGED_METHOD); - } - - private String fragment; - - public UriFragmentUtility() { - // immediate by default - setImmediate(true); - } - - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - target.addVariable(this, "fragment", fragment); - } - - @Override - public void changeVariables(Object source, Map<String, Object> variables) { - super.changeVariables(source, variables); - fragment = (String) variables.get("fragment"); - fireEvent(new FragmentChangedEvent(this)); - } - - /** - * Gets currently set URI fragment. - * <p> - * To listen changes in fragment, hook a {@link FragmentChangedListener}. - * <p> - * Note that initial URI fragment that user used to enter the application - * will be read after application init. It fires FragmentChangedEvent only - * if it is not the same as on server side. - * - * @return the current fragment in browser uri or null if not known - */ - public String getFragment() { - return fragment; - } - - /** - * 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 fireEvent) { - if ((newFragment == null && fragment != null) - || (newFragment != null && !newFragment.equals(fragment))) { - fragment = newFragment; - if (fireEvent) { - fireEvent(new FragmentChangedEvent(this)); - } - 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); - } - -} diff --git a/src/com/vaadin/ui/VerticalLayout.java b/src/com/vaadin/ui/VerticalLayout.java index c40aeaea30..a04d052d98 100644 --- a/src/com/vaadin/ui/VerticalLayout.java +++ b/src/com/vaadin/ui/VerticalLayout.java @@ -3,9 +3,6 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VVerticalLayout; -import com.vaadin.ui.ClientWidget.LoadStyle; - /** * Vertical layout * @@ -19,7 +16,6 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 5.3 */ @SuppressWarnings("serial") -@ClientWidget(value = VVerticalLayout.class, loadStyle = LoadStyle.EAGER) public class VerticalLayout extends AbstractOrderedLayout { public VerticalLayout() { diff --git a/src/com/vaadin/ui/VerticalSplitPanel.java b/src/com/vaadin/ui/VerticalSplitPanel.java index 699bd9287c..0630240e9c 100644 --- a/src/com/vaadin/ui/VerticalSplitPanel.java +++ b/src/com/vaadin/ui/VerticalSplitPanel.java @@ -3,9 +3,6 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VSplitPanelVertical; -import com.vaadin.ui.ClientWidget.LoadStyle; - /** * A vertical split panel contains two components and lays them vertically. The * first component is above the second component. @@ -23,7 +20,6 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * </pre> * */ -@ClientWidget(value = VSplitPanelVertical.class, loadStyle = LoadStyle.EAGER) public class VerticalSplitPanel extends AbstractSplitPanel { public VerticalSplitPanel() { diff --git a/src/com/vaadin/ui/Video.java b/src/com/vaadin/ui/Video.java index ed6588f96a..28fbfb0547 100644 --- a/src/com/vaadin/ui/Video.java +++ b/src/com/vaadin/ui/Video.java @@ -7,7 +7,7 @@ package com.vaadin.ui; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VVideo; +import com.vaadin.terminal.gwt.client.ui.video.VideoConnector; /** * The Video component translates into an HTML5 <video> element and as @@ -30,7 +30,6 @@ import com.vaadin.terminal.gwt.client.ui.VVideo; * @author Vaadin Ltd * @since 6.7.0 */ -@ClientWidget(VVideo.class) public class Video extends AbstractMedia { private Resource poster; @@ -80,7 +79,7 @@ public class Video extends AbstractMedia { public void paintContent(PaintTarget target) throws PaintException { super.paintContent(target); if (getPoster() != null) { - target.addAttribute(VVideo.ATTR_POSTER, getPoster()); + target.addAttribute(VideoConnector.ATTR_POSTER, getPoster()); } } } diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java index 5f6c29f182..3c17baf414 100644 --- a/src/com/vaadin/ui/Window.java +++ b/src/com/vaadin/ui/Window.java @@ -6,15 +6,7 @@ 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; @@ -23,20 +15,17 @@ 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.MouseEvents.ClickEvent; 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.VView; -import com.vaadin.terminal.gwt.client.ui.VWindow; +import com.vaadin.terminal.Vaadin6Component; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.window.WindowServerRpc; +import com.vaadin.terminal.gwt.client.ui.window.WindowState; /** * A component that represents an application (browser native) window or a sub @@ -84,144 +73,15 @@ import com.vaadin.terminal.gwt.client.ui.VWindow; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VWindow.class) -public class Window extends Panel implements URIHandler, ParameterHandler, - FocusNotifier, BlurNotifier { +public class Window extends Panel implements FocusNotifier, BlurNotifier, + Vaadin6Component { - /** - * <b>Application window only</b>. A border style used for opening resources - * in a window without a border. - */ - public static final int BORDER_NONE = 0; - - /** - * <b>Application window only</b>. A border style used for opening resources - * in a window with a minimal border. - */ - public static final int BORDER_MINIMAL = 1; - - /** - * <b>Application window only</b>. A border style that indicates that the - * default border style should be used when opening resources. - */ - public static final int BORDER_DEFAULT = 2; - - /** - * <b>Application window only</b>. The user terminal for this window. - */ - private Terminal terminal = null; - - /** - * <b>Application window only</b>. The application this window is attached - * to or null. - */ - private Application application = null; - - /** - * <b>Application window only</b>. List of URI handlers for this window. - */ - private LinkedList<URIHandler> uriHandlerList = null; - - /** - * <b>Application window only</b>. List of parameter handlers for this - * window. - */ - private LinkedList<ParameterHandler> parameterHandlerList = null; - - /** - * <b>Application window only</b>. List of sub windows in this window. A sub - * window cannot have other sub windows. - */ - private final LinkedHashSet<Window> subwindows = new LinkedHashSet<Window>(); - - /** - * <b>Application window only</b>. Explicitly specified theme of this window - * or null if the application theme should be used. - */ - private String theme = null; - - /** - * <b>Application window only</b>. Resources to be opened automatically on - * next repaint. The list is automatically cleared when it has been sent to - * the client. - */ - private final LinkedList<OpenResource> openList = new LinkedList<OpenResource>(); + private WindowServerRpc rpc = new WindowServerRpc() { - /** - * <b>Application window only</b>. Unique name of the window used to - * identify it. - */ - private String name = null; - - /** - * <b>Application window only.</b> Border mode of the Window. - */ - private int border = BORDER_DEFAULT; - - /** - * <b>Sub window only</b>. Top offset in pixels for the sub window (relative - * to the parent application window) or -1 if unspecified. - */ - private int positionY = -1; - - /** - * <b>Sub window only</b>. Left offset in pixels for the sub window - * (relative to the parent application window) or -1 if unspecified. - */ - private int positionX = -1; - - /** - * <b>Application window only</b>. 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<Notification> notifications; - - /** - * <b>Sub window only</b>. Modality flag for sub window. - */ - private boolean modal = false; - - /** - * <b>Sub window only</b>. Controls if the end user can resize the window. - */ - private boolean resizable = true; - - /** - * <b>Sub window only</b>. Controls if the end user can move the window by - * dragging. - */ - private boolean draggable = true; - - /** - * <b>Sub window only</b>. Flag which is true if the window is centered on - * the screen. - */ - private boolean centerRequested = false; - - /** - * Should resize recalculate layouts lazily (as opposed to immediately) - */ - private boolean resizeLazy = false; - - /** - * Component that should be focused after the next repaint. Null if no focus - * change should take place. - */ - private Focusable pendingFocus; - - /** - * <b>Application window only</b>. 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<String> 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; + public void click(MouseEventDetails mouseDetails) { + fireEvent(new ClickEvent(Window.this, mouseDetails)); + } + }; /** * Creates a new unnamed window with a default layout. @@ -250,7 +110,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, */ public Window(String caption, ComponentContainer content) { super(caption, content); - setScrollable(true); + registerRpc(rpc); setSizeUndefined(); } @@ -269,288 +129,8 @@ public class Window extends Panel implements URIHandler, ParameterHandler, super.addComponent(c); } - /** - * <b>Application window only</b>. Gets the user terminal. - * - * @return the user terminal - */ - public Terminal getTerminal() { - return terminal; - } - /* ********************************************************************* */ - /** - * Gets the parent window of the component. - * <p> - * This is always the window itself. - * </p> - * <p> - * <b>This method is not meant to be overridden. Due to CDI requirements we - * cannot declare it as final even though it should be final.</b> - * </p> - * - * @see Component#getWindow() - * @return the window itself - */ - @Override - public Window getWindow() { - return this; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractComponent#getApplication() - */ - @Override - public Application getApplication() { - if (getParent() == null) { - return application; - } - return getParent().getApplication(); - } - - /** - * Gets the parent component of the window. - * - * <p> - * 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. - * </p> - * <p> - * <b>This method is not meant to be overridden. Due to CDI requirements we - * cannot declare it as final even though it should be final.</b> - * </p> - * - * - * @return the parent window - * @see Component#getParent() - */ - @Override - public Window getParent() { - return (Window) super.getParent(); - } - - /* ********************************************************************* */ - - /** - * <b>Application window only</b>. 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<URIHandler>(); - } - synchronized (uriHandlerList) { - if (!uriHandlerList.contains(handler)) { - uriHandlerList.addLast(handler); - } - } - } - } - - /** - * <b>Application window only</b>. 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; - } - } - } - } - - /** - * <b>Application window only</b>. 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; - } - - /* ********************************************************************* */ - - /** - * <b>Application window only</b>. 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<ParameterHandler>(); - } - synchronized (parameterHandlerList) { - if (!parameterHandlerList.contains(handler)) { - parameterHandlerList.addLast(handler); - } - } - } - - } - - /** - * <b>Application window only</b>. 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; - } - } - } - } - - /** - * <b>Application window only</b>. 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<String, String[]> parameters) { - if (parameterHandlerList != null) { - Object[] handlers; - synchronized (parameterHandlerList) { - handlers = parameterHandlerList.toArray(); - } - for (int i = 0; i < handlers.length; i++) { - ((ParameterHandler) handlers[i]).handleParameters(parameters); - } - } - } - - /* ********************************************************************* */ - - /** - * <b>Application window only</b>. Gets the theme for this window. - * <p> - * 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 - * </p> - * <p> - * Subwindows do not support themes and return the theme used by the parent - * window - * </p> - * - * @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; - } - - /** - * <b>Application window only</b>. 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) * @@ -559,505 +139,13 @@ public class Window extends Panel implements URIHandler, ParameterHandler, @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 (resizeLazy) { - target.addAttribute(VView.RESIZE_LAZY, resizeLazy); - } - - 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<OpenResource> 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<Window> 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<Notification> 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()); - } - if (!n.isHtmlContentAllowed()) { - target.addAttribute( - VView.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED, true); - } - 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. - * <p> - * 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 - * <code>null</code> window name is also a special case. - * </p> - * <p> - * "", 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. - * </p> - * <p> - * "_blank" as {@code windowName} causes the resource to always be opened in - * a new window or tab (depends on the browser and browser settings). - * </p> - * <p> - * "_top" and "_parent" as {@code windowName} works as specified by the HTML - * standard. - * </p> - * <p> - * 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. - * </p> - * - * @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. - * <p> - * Note! This method can not be used for portlets. - * </p> - * - * @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"); - } - } - - /** - * <b>Application window only</b>. Gets the unique name of the window. The - * name of the window is used to uniquely identify it. - * <p> - * 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. - * </p> - * <p> - * Note! Portlets do not support direct window access through URLs. - * </p> - * - * @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}. - * <p> - * <b>Note!</b> Setting this seems to currently have no effect whatsoever on - * the window. - * </p> - * - * @param border - * the border style to set - */ - public void setBorder(int border) { - this.border = border; - } - - /** - * Sets the application this window is attached to. - * - * <p> - * 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. - * </p> - * <p> - * This method invokes {@link Component#attach()} and - * {@link Component#detach()} methods when necessary. - * <p> - * - * @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(); - } - } - - /** - * <b>Application window only</b>. Sets the unique name of the window. The - * name of the window is used to uniquely identify it inside the - * application. - * <p> - * 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. - * </p> - * <p> - * This method can only be called before the window is added to an - * application. - * </p> - * <p> - * Note! Portlets do not support direct window access through URLs. - * </p> - * - * @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"); - } } /* @@ -1068,6 +156,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, @Override public void changeVariables(Object source, Map<String, Object> variables) { + // TODO Are these for top level windows or sub windows? boolean sizeHasChanged = false; // size is handled in super class, but resize events only in windows -> // so detect if size change occurs before super.changeVariables() @@ -1137,17 +226,15 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * {@link CloseListener}. * </p> */ - protected void close() { - Window parent = getParent(); - if (parent == null) { - fireClose(); - } else { + public void close() { + Root root = getRoot(); + // Don't do anything if not attached to a root + if (root != null) { // focus is restored to the parent window - parent.focus(); - - // subwindow is removed from parent - parent.removeWindow(this); + root.focus(); + // subwindow is removed from the root + root.removeWindow(this); } } @@ -1160,7 +247,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * @since 4.0.0 */ public int getPositionX() { - return positionX; + return getState().getPositionX(); } /** @@ -1188,8 +275,8 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * @since 6.3.4 */ private void setPositionX(int positionX, boolean repaintRequired) { - this.positionX = positionX; - centerRequested = false; + getState().setPositionX(positionX); + getState().setCentered(false); if (repaintRequired) { requestRepaint(); } @@ -1205,7 +292,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * @since 4.0.0 */ public int getPositionY() { - return positionY; + return getState().getPositionY(); } /** @@ -1235,8 +322,8 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * @since 6.3.4 */ private void setPositionY(int positionY, boolean repaintRequired) { - this.positionY = positionY; - centerRequested = false; + getState().setPositionY(positionY); + getState().setCentered(false); if (repaintRequired) { requestRepaint(); } @@ -1413,136 +500,53 @@ public class Window extends Panel implements URIHandler, ParameterHandler, fireEvent(new ResizeEvent(this)); } - private void attachWindow(Window w) { - subwindows.add(w); - w.setParent(this); - requestRepaint(); - } - - /** - * Adds a window inside another window. - * - * <p> - * 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. - * </p> - * - * <p> - * Only one level of subwindows are supported. Thus you can add windows - * inside such windows whose parent is <code>null</code>. - * </p> - * - * @param window - * @throws IllegalArgumentException - * if a window is added inside non-application level window. - * @throws NullPointerException - * if the given <code>Window</code> is <code>null</code>. - */ - 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 + * Used to keep the right order of windows if multiple windows are brought + * to front in a single changeset. If this 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; + private Integer bringToFront = null; /** - * If there are currently several sub windows visible, calling this method - * makes this window topmost. + * If there are currently several windows visible, calling this method makes + * this window topmost. * <p> - * 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. + * This method can only be called if this window connected a root. 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. * <p> - * <strong> Note, this API works on sub windows only. Browsers can't reorder - * OS windows.</strong> */ public void bringToFront() { - Window parent = getParent(); - if (parent == null) { + Root root = getRoot(); + if (root == null) { throw new IllegalStateException( "Window must be attached to parent before calling bringToFront method."); } - for (Window w : parent.getChildWindows()) { - if (w.isModal() && !isModal()) { + int maxBringToFront = -1; + for (Window w : root.getWindows()) { + if (!isModal() && w.isModal()) { throw new IllegalStateException( - "There are modal windows currently visible, non-modal window cannot be brought to front."); + "The root contains modal windows, non-modal window cannot be brought to front."); + } + if (w.bringToFront != null) { + maxBringToFront = Math.max(maxBringToFront, + w.bringToFront.intValue()); } } - bringToFront = getParent().bringToFrontSequence++; + bringToFront = Integer.valueOf(maxBringToFront + 1); requestRepaint(); } /** - * Get the set of all child windows. - * - * @return Set of child windows. - */ - public Set<Window> getChildWindows() { - return Collections.unmodifiableSet(subwindows); - } - - /** * Sets sub-window modal, so that widgets behind it cannot be accessed. * <b>Note:</b> affects sub-windows only. * - * @param modality + * @param modal * true if modality is to be turned on */ - public void setModal(boolean modality) { - modal = modality; + public void setModal(boolean modal) { + getState().setModal(modal); center(); requestRepaint(); } @@ -1551,7 +555,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * @return true if this window is modal. */ public boolean isModal() { - return modal; + return getState().isModal(); } /** @@ -1560,8 +564,8 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * @param resizable * true if resizability is to be turned on */ - public void setResizable(boolean resizeability) { - resizable = resizeability; + public void setResizable(boolean resizable) { + getState().setResizable(resizable); requestRepaint(); } @@ -1570,7 +574,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * @return true if window is resizable by the end-user, otherwise false. */ public boolean isResizable() { - return resizable; + return getState().isResizable(); } /** @@ -1579,7 +583,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * sizes are recalculated immediately. */ public boolean isResizeLazy() { - return resizeLazy; + return getState().isResizeLazy(); } /** @@ -1595,7 +599,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * calculate immediately. */ public void setResizeLazy(boolean resizeLazy) { - this.resizeLazy = resizeLazy; + getState().setResizeLazy(resizeLazy); requestRepaint(); } @@ -1604,534 +608,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * 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"). - * - * Care should be taken to to avoid XSS vulnerabilities as the caption is - * rendered as html. - * - * @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. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption is - * rendered as html. - * - * @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"). - * - * Care should be taken to to avoid XSS vulnerabilities as the caption and - * description are rendered as html. - * - * @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. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption and - * description are rendered as html. - * - * @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 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. - * - * Care should be taken to avoid XSS vulnerabilities if html content is - * allowed. - * - * @see #showNotification(com.vaadin.ui.Window.Notification) - * @see Notification - * - * @param caption - * The message caption - * @param description - * The message description - * @param type - * The type of message - * @param htmlContentAllowed - * Whether html in the caption and description should be - * displayed as html or as plain text - */ - public void showNotification(String caption, String description, int type, - boolean htmlContentAllowed) { - addNotification(new Notification(caption, description, type, - htmlContentAllowed)); - } - - /** - * 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<Notification>(); - } - 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". - * <p> - * 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. - * </p> - * <p> - * 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: - * <ul> - * <li>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. - * </li> - * <li>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.</li> - * <li>TYPE_ERROR_MESSAGE requires to user to click it before disappearing, - * and can be used for critical messages.</li> - * <li>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.</li> - * </ul> - * </p> - * <p> - * 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. - * </p> - * - */ - 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; - private boolean htmlContentAllowed; - - /** - * Creates a "humanized" notification message. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption - * is by default rendered as html. - * - * @param caption - * The message to show - */ - public Notification(String caption) { - this(caption, null, TYPE_HUMANIZED_MESSAGE); - } - - /** - * Creates a notification message of the specified type. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption - * is by default rendered as html. - * - * @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. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption - * and description are by default rendered as html. - * - * @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. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption - * and description are by default rendered as html. - * - * @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, description, type, true); - } - - /** - * Creates a notification message of the specified type, with a bigger - * caption and smaller description. - * - * Care should be taken to to avoid XSS vulnerabilities if html is - * allowed. - * - * @param caption - * The message caption - * @param description - * The message description - * @param type - * The type of message - * @param htmlContentAllowed - * Whether html in the caption and description should be - * displayed as html or as plain text - */ - public Notification(String caption, String description, int type, - boolean htmlContentAllowed) { - this.caption = caption; - this.description = description; - this.htmlContentAllowed = htmlContentAllowed; - 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; - } - - /** - * Sets whether html is allowed in the caption and description. If set - * to true, the texts are passed to the browser as html and the - * developer is responsible for ensuring no harmful html is used. If set - * to false, the texts are passed to the browser as plain text. - * - * @param htmlContentAllowed - * true if the texts are used as html, false if used as plain - * text - */ - public void setHtmlContentAllowed(boolean htmlContentAllowed) { - this.htmlContentAllowed = htmlContentAllowed; - } - - /** - * Checks whether caption and description are interpreted as html or - * plain text. - * - * @return true if the texts are used as html, false if used as plain - * text - * @see #setHtmlContentAllowed(boolean) - */ - public boolean isHtmlContentAllowed() { - return htmlContentAllowed; - } - } - - /** - * Executes JavaScript in this window. - * - * <p> - * 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. - * </p> - * - * <p> - * 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. - * </p> - * - * @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<String>(); - } - - jsExecQueue.add(script); - + getState().setCentered(true); requestRepaint(); } @@ -2183,7 +660,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * true if the sub window can be dragged by the user */ public boolean isDraggable() { - return draggable; + return getState().isDraggable(); } /** @@ -2196,7 +673,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler, * true if the sub window can be dragged by the user */ public void setDraggable(boolean draggable) { - this.draggable = draggable; + getState().setDraggable(draggable); requestRepaint(); } @@ -2344,40 +821,18 @@ public class Window extends Panel implements URIHandler, ParameterHandler, */ @Override public void focus() { - if (getParent() != null) { - /* - * When focusing a sub-window it basically means it should be - * brought to the front. Instead of just moving the keyboard focus - * we focus the window and bring it top-most. - */ - bringToFront(); - } else { - super.focus(); - } - } - - /** - * Notifies the child components and subwindows that the window is attached - * to the application. - */ - @Override - public void attach() { - super.attach(); - for (Window w : subwindows) { - w.attach(); - } + /* + * When focusing a sub-window it basically means it should be brought to + * the front. Instead of just moving the keyboard focus we focus the + * window and bring it top-most. + */ + super.focus(); + bringToFront(); } - /** - * Notifies the child components and subwindows that the window is detached - * from the application. - */ @Override - public void detach() { - super.detach(); - for (Window w : subwindows) { - w.detach(); - } + public WindowState getState() { + return (WindowState) super.getState(); } } diff --git a/src/com/vaadin/ui/themes/BaseTheme.java b/src/com/vaadin/ui/themes/BaseTheme.java index c652a8a675..6f448746bf 100644 --- a/src/com/vaadin/ui/themes/BaseTheme.java +++ b/src/com/vaadin/ui/themes/BaseTheme.java @@ -50,4 +50,10 @@ public class BaseTheme { */ public static final String TREE_CONNECTORS = "connectors"; + /** + * Clips the component so it will be constrained to its given size and not + * overflow. + */ + public static final String CLIP = "v-clip"; + }
\ No newline at end of file |